Estatística para Ciência de Dados

Contents

Estatística para Ciência de Dados#

Programa#

Objetivos: Fornecer conhecimento em descrição e sumarização de dados, probabilidade, inferência estatística, inferência Bayesiana e modelos de regressão, necessários para o desenvolvimento de procedimentos em ciências de dados.

Ementa:

  1. Descritiva: Medidas de posição, Medidas de dispersão, Agrupamento de dados, Apresentação tabular, Representação Gráfica

  2. Probabilidade: Distribuições de probabilidade, esperança, variância e covariância, Resultados assintóticos e suas aplicações.

  3. Elementos de inferência estatística: Funções de evidência e verossimilhança, Procedimentos de estimação pontual, Intervalos de confiança e testes de hipóteses, Inferência baseada em simulação.

  4. Inferência Bayesiana: O paradigma Bayesiano, Os diferentes tipos de prioris, Distribuições conjugadas, Estimação Bayesiana, Densidade preditiva.

  5. Modelagem de Regressão: Modelos lineares, Seleção de modelos, Regressão multivariada.

Referências:

  1. Casella, G. and Berger, R. (2002). Statistical Inference. 2nd Edition, Duxbury Press, Florida.

  2. Migon, H. S., Gamerman, D. and Louzada, F. (2014). Statistical Inference: An Integrated Approach, Second Edition, CRC Press.

  3. Caffo, B. (2016). Statistical Inference for Data Science. Leanpub. Disponível em https://leanpub.com/LittleInferenceBook

Alguns vídeos complementares sugeridos:

… e outras que serão citadas ao longo do curso.

Aula 1. Visualização de dados - Análise descritiva#

Programa#

a. Medidas de posição ou localização

b. Medidas de dispersão

c. Agrupamento de dados

d. Apresentação tabular

e. Representação Gráfica

Referências e motivação:#

Análise exploratória de dados#

Análise descritiva ou análise exploratória de dados (AED) tem como objetivos básicos:

  • explorar os dados para descobrir ou identificar aspectos ou padrões de maior interesse,

  • representar os dados de forma a destacar ou chamar a atenção para aspectos ou padrões que podem ou não se confirmar inferencialmente.

Tukey (1977) chama a análise exploratória de dados de trabalho de detetive, que busca pistas e evidência, e a análise confirmatória de dados é um trabalho judicial ou quase-judicial, que analisa e avalia a força das provas e da evidência.

Tukey também diz que: “A análise exploratória de dados nunca conta a história toda, mas nada é tão perfeito para ser considerado a pedra fundamental, um primeiro passo para a análise de dados”.

É importante salientar que a AED é um trabalho inicial, a pedra fundamental, e os resultados devem ser analisados com uma análise confirmatória.

Tukey, John W. (1977) Exploratory data analysis. Editora Addison-Wesley.

AED.png

A natureza dos dados#

Nesta aula, e quase sempre neste curso de Estatística para Ciência de Dados, trataremos de dados retangulares, que tem nas linhas as unidades amostrais (exemplos, samples) e nas colunas as variáveis (atributos, features).

Tipos de variáveis#

  • Qualitativas (não-numéricas)

    • Nominais: sexo, cor da pele, fumante/não-fumante, adimplente/inadimplente

    • Ordinais: escolaridade (em categorias), grau de satisfação, idade (em faixas)

  • Quantitativas (numéricas)

    • Discretas: número de defeitos em uma peça, número de produtos contratados

    • Contínuas: peso, idade, pressão sanguínea, valor contratado de um produto

Medidas-resumo#

  • Medidas de posição

    • Média: boas propriedades estatísticas

    • Mediana: medida resistente

    • Moda: valor mais frequente

    • Quantis: caracterização da distribuição dos dados

  • Medidas de dispersão

    • Desvio-padrão

    • Variância

    • Amplitude (range)

    • Coeficiente de variação: medida de dispersão relativa

  • Assimetria: Assimetria da distribuição dos dados

  • Curtose: Achatamento da distribuição

  • Medidas de associação: Covariância, Coeficiente de correlação de Pearson, Coeficiente de correlação de Spearman

Medidas de posição#

Daqui em diante, vamos estabelecer \(X_1,\ldots, X_n\) é uma amostra aleatória e \(x_1,\ldots, x_n\) os dados observados dessa amostra. As medidas aqui apresentadas são amostrais e são obtidas a partir de \(x_1,\ldots, x_n\).

A média (amostral observada) é definida como

\(\bar{x} = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n} x_i}{n}}\)

Considere agora os dados ordenados \(x_{(1)},\ldots, x_{(n)}\), isto é,

\(x_{(1)} = min(x_1,\ldots, x_n)\) e \(x_{(n)} = max(x_1,\ldots, x_n)\).

Se \(n\) é ímpar, a posição central é \(c = (n + 1) / 2\). Se \(n\) é par, as posições centrais são \(c = n / 2\) e \(c + 1 = n / 2 + 1\).

A mediana é definida como

\(Md = \bigg \{\begin{array}{l}x_{(c)}, \mbox{se n é ímpar}\\ \displaystyle\frac{x_{(c)}+x_{(c+1)}}{2}, \mbox{se n é par}\end{array}\)

A moda é o valor mais frequente da amostra. Não necessariamente existe.

Um quantil é o valor que provoca uma divisão conveniente nos valores ordenados. O quantil de 10% divide os dados de tal forma que 10% dos menores valores fiquem “à sua esquerda”. O quantil de 50% é a mediana.

Os quartis dividem os dados em porções de 25%.

Os decis dividem os dados em porções de 10%.

Os percentis dividem os dados em porções de 1%.

Medidas de dispersão#

A variância amostral é dada por

\(s^2 = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n}}.\)

O desvio padrão é dado por

\(s = \displaystyle\sqrt{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n}}.\)

É comum, entretanto, utilizar as medidas corrigidas:

Variância amostral corrigida:

\(s^2 = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n-1}}\)

Desvio padrão corrigido:

\(s = \displaystyle\sqrt{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})^2}{n-1}}\)

A amplitude é dada por

\(A = x_{(n)} -x_{(1)}.\)

O coeficiente de variação (amostral) é dado pela razão entre o desvio-padrão e a média

\(CV = \displaystyle{\frac{s}{\bar{x}}}\)

Assimetria#

  • Distribuição simétrica: média = mediana = moda

  • Distribuição assimétrica à direita: moda < mediana < média

  • Distribuição assimétrica à esquerda: média < mediana < moda

Curtose#

  • Distribuições mesocúrticas: achatamento da distribuição normal

  • Distribuições leptocúrticas: distribuição mais concentrada

  • Distribuições platicúrticas: distribuição mais achatada

# Ilustração das medidas média, moda, mediana para dados simétricos
# Adaptado de https://stackoverflow.com/questions/51417483/mean-median-mode-lines-showing-only-in-last-graph-in-seaborn/51417635

from matplotlib import pyplot as plt
import pandas as pd
import seaborn as sns

df = pd.DataFrame({"rating": [5, 6, 6, 7, 7, 7, 7, 8, 8, 9]})

f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw= {"height_ratios": (0.2, 1)})
mean=df['rating'].mean()
median=df['rating'].median()
mode=df['rating'].mode().values[0]

sns.boxplot(data=df, x="rating", ax=ax_box)
ax_box.axvline(mean, color='r', linestyle='--')
ax_box.axvline(median, color='g', linestyle='-')
ax_box.axvline(mode, color='b', linestyle='-')

sns.histplot(data=df, x="rating", ax=ax_hist, kde=True)
ax_hist.axvline(mean, color='r', linestyle='--', label="Mean")
ax_hist.axvline(median, color='g', linestyle='-', label="Median")
ax_hist.axvline(mode, color='b', linestyle='-', label="Mode")

plt.legend()

ax_box.set(xlabel='')
plt.show()
../_images/104315675084ca8b9c4fd2bead1518d53182be6c4471f51e0ccaac06c5ec662f.png
# Ilustração das medidas média, moda, mediana para dados assimétricos à direita ou assimetria positiva
# Adaptado de https://stackoverflow.com/questions/51417483/mean-median-mode-lines-showing-only-in-last-graph-in-seaborn/51417635

from matplotlib import pyplot as plt
import pandas as pd
import seaborn as sns
import statistics

df = pd.DataFrame({"rating": [1,1,1,2,2,3,4,5,5,10]})

f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw= {"height_ratios": (0.2, 1)})
mean=df['rating'].mean()
median=df['rating'].median()
mode=df['rating'].mode().values[0]

sns.boxplot(data=df, x="rating", ax=ax_box)
ax_box.axvline(mean, color='r', linestyle='--')
ax_box.axvline(median, color='g', linestyle='-')
ax_box.axvline(mode, color='b', linestyle='-')

sns.histplot(data=df, x="rating", ax=ax_hist, kde=True)
ax_hist.axvline(mean, color='r', linestyle='--', label="Mean")
ax_hist.axvline(median, color='g', linestyle='-', label="Median")
ax_hist.axvline(mode, color='b', linestyle='-', label="Mode")

plt.legend()


ax_box.set(xlabel='')
plt.show()
../_images/4ee1b5897855eaecaca64e31fd3bb1ccff7a3e5c32b437ed98b881585b1cbcbf.png
# Ilustração das medidas média, moda, mediana para dados assimétricos à esquerda ou com assimetria negativa
# Adaptado de: https://stackoverflow.com/questions/51417483/mean-median-mode-lines-showing-only-in-last-graph-in-seaborn/51417635


from matplotlib import pyplot as plt
import pandas as pd
import seaborn as sns
import statistics

df = pd.DataFrame({"rating": [1, 4, 6, 8, 8, 9, 10, 10, 10, 10]})

f, (ax_box, ax_hist) = plt.subplots(2, sharex=True, gridspec_kw= {"height_ratios": (0.2, 1)})
mean=df['rating'].mean()
median=df['rating'].median()
mode = statistics.mode(df['rating'])

sns.boxplot(data=df, x="rating", ax=ax_box)
ax_box.axvline(mean, color='b', linestyle='--')
ax_box.axvline(median, color='r', linestyle='-')
ax_box.axvline(mode, color='g', linestyle='-')

sns.histplot(data=df, x="rating", ax=ax_hist, kde=True)
ax_hist.axvline(mean, color='b', linestyle='--', label="Mean")
ax_hist.axvline(median, color='r', linestyle='-', label="Median")
ax_hist.axvline(mode, color='g', linestyle='-', label="Mode")

plt.legend()

ax_box.set(xlabel='')
plt.show()
../_images/9965985ada7840f8cbd1de7b68e9ec752b5c0bd6c562722d0194ea40f97c31ec.png

Curtose#

Referências:

Medida que caracteriza o achatamento da curva.

  • Curtose \(\approx 0\): achatamento da curva normal

  • Curtose \(>0\): leptocúrtica, distribuição mais afunilada

  • Curtose \(<0\): platicúrtica, distribuição mais achatada

Obs: Distribuição normal https://www.spss-tutorials.com/normal-distribution/

from scipy.stats import norm, kurtosis

data = norm.rvs(size=100000)

kurtosis(data)
0.009731018571984329
# Fonte: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.kurtosis.html

import matplotlib.pyplot as plt
import scipy.stats as stats
from scipy.stats import kurtosis
import numpy as np


x = np.linspace(-5, 5, 100)
ax = plt.subplot()
distnames = ['laplace', 'norm', 'uniform']

for distname in distnames:
    if distname == 'uniform':
        dist = getattr(stats, distname)(loc=-2, scale=4)
    else:
        dist = getattr(stats, distname)
    data = dist.rvs(size=1000)
    kur = kurtosis(data, fisher=True)
    y = dist.pdf(x)
    ax.plot(x, y, label="{}, {}".format(distname, round(kur, 3)))
    ax.legend()
       
# Normal: mesocúrtica
# Laplace: leptocúrtica
# Uniforme: platicúrtica
../_images/793650d7eaa96149b0f26e74c1af41cbfd4d183bb9b3e5e1b71e7f63fd69686e.png

Medidas de associação entre variáveis quantitativas#

Sejam \(X\) e \(Y\) variáveis quantitativas de interesse e as amostras aleatórias observadas \(x_1,\ldots,x_n\) e \(y_1,\ldots,y_n\), respectivamente. As medidas de associação mais utilizadas são:

Covariância (amostral)#

\(s_{XY} = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n}(x_i-\bar{x})(y_i-\bar{y})}{n-1}}\)

Coeficiente de correlação linear (amostral) de Pearson#

Referência: https://pt.wikipedia.org/wiki/Coeficiente_de_correlação_de_Pearson

\(r = \displaystyle{\frac{s_{XY}}{\sqrt{s^2_X s^2_Y }}}\)

Propriedade:

\(-1 \leq r \leq 1\)

É comum usar as seguintes classificações:

  1. \(r=1\) indica uma correlação perfeita e positiva

  2. \(r=-1\) indica uma correlação perfeita e negativa

  3. \(0.7 \leq |r| \leq 1\) indica uma correlação forte

  4. \(0.5 \leq |r| \leq 0.69\) indica uma correlação moderada

  5. \(0 \leq |r| \leq 0.49\) indica uma correlação fraca

Coeficiente de correlação de Spearman#

Avalia relações monótonas entre duas variáveis

Referência: https://pt.wikipedia.org/wiki/Coeficiente_de_correlação_de_postos_de_Spearman

Associação entre variáveis qualitativas e quantitativas#

Alguns casos que veremos mais adiante:

  • Associação entre variáveis quantitativas e qualitativas: Testes para comparação de médias em duas populações.

  • Associação entre variáveis qualitativas: Teste qui-quadrado, teste exato de Fisher, entre outros.

Representação gráfica e tabular de dados#

  • Variáveis qualitativas:

    • Tabela de frequência: Resume a informação dos dados de forma a possibilitar a observação de frequências absolutas ou relativas de cada categoria das variáveis qualitativas (ou valores assumidos pelas variáveis quantitativas discretas).

    • Gráfico de barras Representação gráfica das frequências de cada categoria das variáveis qualitativas (ou valores assumidos pelas variáveis quantitativas discretas). As barras são separadas.

    • Gráfico de Pareto: Gráfico de barras + frequências acumuladas das categorias.

    • Gráfico de setores (pizza): Representação gráfica das proporções das categorias das variáveis quantitativas discretas.

  • Variáveis quantitativas discretas:

    • Tabelas de frequências

    • Gráficos de barras

    • Gráficos de pontos

  • Variáveis quantitativas contínuas:

    • Histogramas: Representação gráfica para uma aproximação da distribuição de uma variável quantitativa contínua, discretizada em classes de tamanhos convenientes. As barras são adjacentes. Permitem observar a localização, dispersão, assimetria, número de picos, curtose dos dados.

    • Gráficos de linhas (dados coletados ao longo do tempo)

    • Boxplots (gráficos de caixas): Representação gráfica inteligente que permite a observação da localização, dispersão, assimetria, pontos discrepantes (outliers). Além disso, permite comparar visualemente a distribuição de dados em dois grupos. Pode indicar evidências sobre a igualdade das médias entre os dados de dois grupos, pendente de análise confirmatória inferencial.

boxplot.png

Representação tabular#

Tabelas que resumem a informação da base completa de dados.

  • Tabelas de frequências: Resumo dos dados originais considerando as frequências observadas na amostra, de variáveis qualitativas ou variáveis que foram categorizadas

  • Tabelas de dupla entrada: Avaliação da associação entre variáveis qualitativas ou que foram categorizadas.

Aplicação com visualização e exploração de dados#

Considere uma amostra de 10 mil clientes de um banco no arquivo dados_banco.csv. Estão disponíveis as variáveis:

  • Cliente: Identificador do cliente.

  • Sexo: Feminino (F) ou Masculino (M)

  • Idade: Idade do cliente, em anos completos.

  • Empresa: Tipo da empresa em que trabalha: Pública, Privada ou Autônomo

  • Salário: Salário declarado pelo cliente na abertura da conta, em reais.

  • Saldo_cc: Saldo em conta corrente, em reais.

  • Saldo_poupança: Saldo em poupança, em reais.

  • Saldo_investimento: Saldo em investimentos, em reais.

  • Devedor_cartao: Valor em atraso no cartão de crédito, em reais.

  • Inadimplente: Se o cliente é considerado inadimplente atualmente (1) ou não (0), de acordo com critérios preestabelecidos.

Desenvolva a exploração e visualização dos dados. Verifique possíveis associações entre variáveis.

import os.path
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy import stats

%matplotlib inline

# Modifique o diretório para fazer a leitura dos dados em dados_banco.csv

# Dados banco - Leitura dos dados
# Caso necessário, leia a partir de um diretório da sua máquina
# pkgdir = '/hdd/MBA/ECD/Data'
# dados = pd.read_csv(f'{pkgdir}/dados_banco.csv', index_col=0)

dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)

dados
Sexo Idade Empresa Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
Cliente
75928 M 32 Privada 5719.00 933.79 0.0 0.0 6023.68 0
52921 F 28 Privada 5064.00 628.37 0.0 0.0 1578.24 0
8387 F 24 Autônomo 4739.00 889.18 0.0 0.0 2578.70 0
54522 M 30 Pública 5215.00 1141.47 0.0 0.0 4348.96 0
45397 M 30 Autônomo 5215.56 520.70 0.0 0.0 1516.78 1
... ... ... ... ... ... ... ... ... ...
33487 F 31 Pública 5016.00 498.96 0.0 0.0 1263.34 0
71360 M 29 Pública 5329.00 1142.82 0.0 0.0 5613.71 0
92455 M 34 Privada 5581.00 885.34 0.0 0.0 1199.22 0
61296 F 28 Privada 5061.00 660.74 0.0 0.0 1152.97 0
52862 M 33 Autônomo 5519.00 1147.71 0.0 0.0 4684.66 0

10000 rows × 9 columns

dados.head()
Sexo Idade Empresa Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
Cliente
75928 M 32 Privada 5719.00 933.79 0.0 0.0 6023.68 0
52921 F 28 Privada 5064.00 628.37 0.0 0.0 1578.24 0
8387 F 24 Autônomo 4739.00 889.18 0.0 0.0 2578.70 0
54522 M 30 Pública 5215.00 1141.47 0.0 0.0 4348.96 0
45397 M 30 Autônomo 5215.56 520.70 0.0 0.0 1516.78 1

Classificação das variáveis por tipo#

  • Sexo: qualitativa nominal

  • Idade: quantitativa contínua

  • Empresa: qualitativa nominal

  • Salário: quantitativa contínua

  • Saldo_cc: quantitativa contínua

  • Saldo_poupança: quantitativa contínua

  • Saldo_investimento: quantitativa contínua

  • Devedor_cartão: quantitativa contínua

  • Inadimplente: qualitativa nominal (embora numérica)

Tabela de frequências (absolutas e relativas)#

(para a Empresa, repetir para outras variáveis qualitativas)

# Tabela de frequências absolutas

tab = pd.crosstab(index=dados['Empresa'], columns='count')

tab
col_0 count
Empresa
Autônomo 1447
Privada 6103
Pública 2450
tab = pd.crosstab(index=dados['Empresa'], columns='count')

# Tabela de frequências relativas
tab/tab.sum()
col_0 count
Empresa
Autônomo 0.1447
Privada 0.6103
Pública 0.2450

Análise: Na base de dados, cerca de 61% dos clientes trabalham em empresas privadas, 24% em empresas públicas e 15% são autônomos.

Medidas resumo#

(para a idade, poderia repetir para as outras variáveis quantitativas)

# Média

dados['Idade'].mean()
31.8019
# Mediana

dados['Idade'].median()
32.0
# Desvio-padrão

round(dados['Idade'].std(),2)
2.93
# Média de idade por grupos

dados.groupby('Sexo')['Idade'].mean()
Sexo
F    30.130466
M    33.027734
Name: Idade, dtype: float64

Análise: A média de idade nos dados é 31.8 anos, a mediana é 32 anos. O desvio-padrão da idade na base de dados geral é 2.93 anos. Entre mulheres, a média de idade é 30.1 anos e entre homens, 33 anos.

# Média de idade por grupos

dados.groupby('Empresa')['Idade'].mean()
Empresa
Autônomo    29.163787
Privada     32.867115
Pública     30.706531
Name: Idade, dtype: float64

Análise: A média de idade entre os clientes autônomos é de 29.1 anos, entre clientes que trabalham em empresas privadas é 32.9 anos e para clientes que trabalham em empresas públicas é 30.7 anos.

# Moda - para a Empresa
import statistics

statistics.mode(dados['Empresa'])
'Privada'

Análise: Na base de dados, o tipo de empresa mais comum é a empresa privada.

# Ordenação dos dados

np.sort(dados['Idade'])
array([21, 22, 22, ..., 49, 50, 50], dtype=int64)
# Quantis de 95% e 25%

np.percentile(dados['Idade'],95)
36.0
np.percentile(dados['Idade'],25)
30.0
sns.displot(x=dados['Idade'], height=4, kind='kde');
../_images/e788c2cbffc35f7e8bd9898c59e30ab1a0758c35e0e59f89679b22d76faebcec.png
tab1 = pd.crosstab(index=dados['Sexo'], columns='count')
tab1/tab1.sum()
col_0 count
Sexo
F 0.4231
M 0.5769

Estatísticas descritivas dos dados com describe()#

dados.describe()
Idade Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
count 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000
mean 31.801900 5482.880238 773.441611 2224.517679 1476.939508 2737.210731 0.246100
std 2.931913 393.779438 246.932963 5668.740769 3920.049185 1994.877093 0.430759
min 21.000000 4325.720000 -280.670000 0.000000 0.000000 0.000000 0.000000
25% 30.000000 5207.540000 599.425000 0.000000 0.000000 1186.807500 0.000000
50% 32.000000 5498.780000 766.000000 0.000000 0.000000 2692.935000 0.000000
75% 34.000000 5738.220000 941.470000 0.000000 0.000000 4058.565000 0.000000
max 50.000000 8582.000000 2007.260000 23336.420000 21810.520000 12312.220000 1.000000
dados.loc[:,dados.columns != 'Cliente'].describe()
Idade Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
count 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000
mean 31.801900 5482.880238 773.441611 2224.517679 1476.939508 2737.210731 0.246100
std 2.931913 393.779438 246.932963 5668.740769 3920.049185 1994.877093 0.430759
min 21.000000 4325.720000 -280.670000 0.000000 0.000000 0.000000 0.000000
25% 30.000000 5207.540000 599.425000 0.000000 0.000000 1186.807500 0.000000
50% 32.000000 5498.780000 766.000000 0.000000 0.000000 2692.935000 0.000000
75% 34.000000 5738.220000 941.470000 0.000000 0.000000 4058.565000 0.000000
max 50.000000 8582.000000 2007.260000 23336.420000 21810.520000 12312.220000 1.000000

Gráfico de setores (pizza)#

tab
col_0 count
Empresa
Autônomo 1447
Privada 6103
Pública 2450
plot = tab.plot.pie(y='count')
../_images/856c88ddc229eec490afad01e5a154d9df890723e458f379d4480b0b01b7b4d0.png
#define Seaborn color palette to use
colors = sns.color_palette('pastel')[0:5]
plot = tab.plot.pie(y='count', colors=colors)
../_images/4421bb5eb8dbde74a123d8f974aaf23529a0bc272cf5d4d25955b79bf76c25b3.png
# Tabela de frequências absolutas

tab = pd.crosstab(index=dados['Sexo'], columns='count')

tab
col_0 count
Sexo
F 4231
M 5769
plot = tab.plot.pie(y='count', colors=colors)
../_images/7a83416e54c3fdbbc04b688445394e0af966d7993b54daf92bdc72b7f95381a4.png

Gráfico de barras#

tab.plot.bar()
plt.legend(title='Sexo')

plt.show()
../_images/290d57c4f3c08f8e47c755f5a16cce06304cf8c91501ac066ddf4d671162c021.png

Boxplot#

  • Posição

  • Dispersão

  • Outliers

  • Assimetria

https://seaborn.pydata.org/generated/seaborn.boxplot.html

dados['Salario']
Cliente
75928    5719.00
52921    5064.00
8387     4739.00
54522    5215.00
45397    5215.56
          ...   
33487    5016.00
71360    5329.00
92455    5581.00
61296    5061.00
52862    5519.00
Name: Salario, Length: 10000, dtype: float64
sns.boxplot(y=dados['Salario'], palette='pastel')
<AxesSubplot:ylabel='Salario'>
../_images/e1300ceea8c66c975764a53088292b6f548c5dd062a515b338b2ce51d762944c.png

Histograma

sns.displot(dados['Salario'],kde=False, bins=10, height=5, aspect=2);
../_images/29c0a6d77448124bc98d4cb7b6f6b6d038280682653eb0c850f9ee47a027b38d.png
sns.displot(dados['Salario']);
../_images/6dd2b0cc75b03ca863a6b92549a6e864f98c711e807336749bbf14f9525a9482.png
sns.displot(dados['Salario'], bins=10, height=5, aspect=2)
<seaborn.axisgrid.FacetGrid at 0x17c18c8c1f0>
../_images/29c0a6d77448124bc98d4cb7b6f6b6d038280682653eb0c850f9ee47a027b38d.png

Densidade alisada

sns.displot(dados['Salario'], kind='kde', height=5, aspect=2)
<seaborn.axisgrid.FacetGrid at 0x17c188bd4f0>
../_images/bee2067d26e452558c21b888a3b3e34bf3bf591cb59a485556a069b99d1bac4d.png

Associação entre duas variáveis qualitativas#

# Tabela de dupla entrada

tabela_dupla = pd.crosstab(index=dados['Empresa'], columns=dados['Sexo'])

tabela_dupla
Sexo F M
Empresa
Autônomo 875 572
Privada 2047 4056
Pública 1309 1141
tabela_dupla.plot.bar()

plt.legend(title='Sexo')

plt.show()
../_images/353cd80191be295e27a0f35f7649de3ac557a47cae0f27d1fd7474b984c70e98.png
tabela_dupla.plot.bar(stacked=True)

plt.legend(title='Sexo')

plt.show()
../_images/eda64c012ecd7b9c7ec7e1b6396a1944ad7d6acb52c2f572a91f6f26bac7d952.png

Gráfico de mosaico#

from statsmodels.graphics.mosaicplot import mosaic

plt.rcParams["figure.figsize"] = [10, 5]

mosaic(dados,['Sexo', 'Empresa'] );
../_images/b713e907fc09d072f6e0258b73302f7ea2843f46bd531c8992e29ee174b66156.png

Associação entre variáveis quantitativas e qualitativas#

ax = sns.boxplot(x='Sexo', y='Salario', data=dados, palette='Set2')
../_images/03b26b62aa695745cf0128944ebdf3383f9baf7d9053413ebb3d4a3f3ac58ea1.png
plt.figure(figsize=(12,6))  
ax = sns.boxplot(x='Sexo', y='Salario', hue='Empresa', data=dados, palette='colorblind')
../_images/a489c65e5b681b22f617b2e03302c370dc410e2d4a7e46ba510998a9ae5bfa47.png
ax = sns.boxplot(x='Empresa', y='Salario', hue='Sexo', data=dados, palette='Set2')
../_images/0ba88ea0711e2ef090c5ed3b7a54ba4bcc8512547a61217b3afe60a05c7caec3.png
ax = sns.catplot(x='Sexo', y='Salario', kind='violin', data=dados, palette='Set2')
../_images/7993e475bd4f4d62f35db69597590ca6df32146c0d26680f96f33df9c2dfa487.png
ax = sns.catplot(x='Sexo', y='Salario', hue='Empresa', kind='violin', data=dados, palette='Set2')
../_images/0bdc0a93af32146c0454d682bc4d515b04fc8ae1327d42a4e5c7e0c837cbe819.png
sns.catplot(x='Empresa', y='Salario', hue='Sexo', kind='violin', split=True, data=dados, palette='Set2')
<seaborn.axisgrid.FacetGrid at 0x17c1ac1ffd0>
../_images/1343dd0f3166cdf453606bf1d4c0c744230e84decf9bb0c53e2ac97a01dc0595.png
# Salário médio por tipo de empresa

sns.set_theme(style="whitegrid")

# Estabelecendo o tamanho do gráfico
plt.figure(figsize=(8,4))

# Título
plt.title("Salário médio por tipo de empresa")

# Gráfico de barras com salário médio por tipo de empresa
sns.barplot(x='Empresa', y='Salario', data=dados, palette='Set2')
#sns.barplot(x='Empresa', y='Salario', hue='Sexo', data=dados, palette='Set2')


# Label para eixo vertical
plt.ylabel("Salário");
../_images/0a5dd237acdfb73a7856135c81e014330c0eb619046652030d77146a8b48665a.png

Associação entre variáveis quantitativas#

Gráfico de dispersão

sns.set_palette('colorblind')
sns.relplot(x='Idade', y='Salario', data=dados)
<seaborn.axisgrid.FacetGrid at 0x17c167a2220>
../_images/a163c09cba689ad0206755a16d61ad4e16de4bcda4157eef174528bdd5409872.png
sns.set_palette('colorblind')
sns.relplot(x='Idade', y='Salario', hue='Sexo', col='Empresa',  data=dados);
../_images/17d635a3f0312c20330303600b50dd36d3029e855042877858839dc5d0bc287a.png

Gráficos com Regressão

sns.lmplot(x='Idade', y='Salario', hue='Sexo', col='Empresa',  data=dados, aspect=1, ci=90);
../_images/53f3be9fd9c59d344534d4eba83deff817ba7d57c0b54d1352dbb6e8c2cff030.png
sns.lmplot(x='Idade', y='Salario', hue='Empresa', col='Sexo',  data=dados, aspect=1, ci=90);
../_images/62ef4f943204cce81a8da2becdbf247974b5485bc7e151736ea4f1e60b3f41ec.png

Joint plot

sns.jointplot(x='Idade', y='Salario',   data=dados);
../_images/37e93e47482e4a4f9c52a0c5b03c62dac6bd7661f9098fe798e5c5f9a8b33dda.png
sns.jointplot(x='Idade', y='Salario', kind='reg',  data=dados);
../_images/d80cdd8e1015c6ead22cab79c1cd5ed9e24b33e6b82a3521594de9a5cd7d72a4.png
sns.jointplot(x='Idade', y='Salario', kind='kde',  data=dados);
../_images/e80d982dc5c5e5ff2570fc8b2dd72d88884e370e53be9f16eab5f1d2592d84ba.png

Coeficiente de correlação de Pearson

from scipy.stats import pearsonr

pearsonr(dados['Idade'], dados['Salario'])[0]
0.8506660825874652

Heatmap (mapa de calor)#

dados_heatmap = dados.loc[:,dados.columns != 'Cliente'].groupby(['Idade']).mean()
dados_heatmap.head()

# Estabelecendo o tamanho do gráfico
plt.figure(figsize=(10,5))

ax = sns.heatmap(dados_heatmap)
# ax = sns.heatmap(dados_heatmap, cmap="BuPu")
../_images/6d415a728a04572e29c2b4567cc3180612ce58864d1ac0070b27f18d4bc791cb.png

Gráficos multivariados#

sns.pairplot(dados[['Salario','Saldo_cc', 'Saldo_poupança', 'Saldo_investimento', 'Devedor_cartao']])
<seaborn.axisgrid.PairGrid at 0x17c19ece670>
../_images/7c2b55f3c2df2debb33e1701348655fb1f934e03cc5f79248ee96df1e9dbb458.png
sns.pairplot(dados[['Salario','Saldo_cc', 'Saldo_poupança', 'Saldo_investimento', 'Devedor_cartao']], kind='reg')
<seaborn.axisgrid.PairGrid at 0x17c1ac9f070>
../_images/c9ab8c944b18289749848f3e3a516eda2995cf4ce5ef516c2cd12d4f51580fca.png
dados_nozeros = dados[dados['Saldo_investimento']*dados['Saldo_poupança']!=0]
sns.pairplot(dados_nozeros[['Salario','Saldo_cc', 'Saldo_poupança', 'Saldo_investimento', 'Devedor_cartao']], kind='reg')
<seaborn.axisgrid.PairGrid at 0x17c1e9d2220>
../_images/0f2d09544d0e7a24e88ef072ee8daad61aeaebbe3dbe200001610955ffaa8f16.png

Agrupamento de dados#

  • Agrupamento hierárquico (dendrograma)

  • Agrupamento não-hierárquico (k-médias)

Referências:

Aulas no contexto de Análise Multivariada e Aprendizado Não-supervisionado (Profa. Cibele Russo):

Exercício

Analise as possíveis associações entre o sexo, idade, empresa, salário, saldo em conta corrente, saldo em conta poupança, saldo em investimento e devedor no cartão com a variável Inadimplente.

ax = sns.boxplot(x='Sexo', y='Salario', hue='Inadimplente', data=dados, palette='muted')
../_images/bfae4db052cb013106029daf0235dc8fdece581edb4df654acbbcae88ec6b306.png
ax = sns.boxplot(x='Sexo', y='Saldo_cc', hue='Inadimplente', data=dados, palette='muted')
../_images/29ab83b2a4ef5991e7156659fb771a6d83d45334f8ab0d650b61d18abe2ab547.png
ax = sns.boxplot(x='Sexo', y='Devedor_cartao', hue='Inadimplente', data=dados, palette='muted')
../_images/5819451f596e4803aacbdcb15a4e569a1bf8950ee7f824674617d3a3191f3079.png
dados.loc[dados['Inadimplente']==0, 'Inadimplente']= 'Não'
dados.loc[dados['Inadimplente']==1, 'Inadimplente']= 'Sim'
sns.displot(dados, x='Devedor_cartao', col='Sexo', hue='Inadimplente', bins=30);
../_images/0b5ea345628403cb855a05423fa0163583a181941f6cd41c75bf6cf528c0b980.png

Pacote plotly#

# Instale se necessário
# AVISO: Os códigos abaixo podem não funcionar de imediato no google colab (somente no Jupyter), necessitando de adaptações.
#!pip install plotly==5.3.1
import plotly
import plotly.express as px

#fig = px.histogram(dados, x='Salario')
#fig.show()

#dados['Salario'].iplot(kind='hist')
fig = px.histogram(dados, x='Salario', marginal='box')
fig.show()
fig = px.box(dados, y='Salario', x='Sexo', color='Empresa')
fig.show()
fig = px.scatter(dados, x='Idade', y='Salario', color='Empresa')
fig.show()
fig = px.scatter(dados, x='Saldo_cc', y='Salario', color='Empresa')
fig.show()

AED de forma automatizada#

Usar somente se estritamente necessário ou como uma análise inicial!!

Referência: https://medium.com/geekculture/10-automated-eda-libraries-at-one-place-ea5d4c162bbb

# ATENÇÃO: Para executar o notebook no colab, no menu Runtime, selecione Restart runtime e execute normalmente os comandos abaixo
import pandas as pd
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)
# Instale se necessário ou se for executar no Google colab
!pip install pandas_profiling
Collecting pandas_profiling
  Using cached pandas_profiling-3.6.6-py2.py3-none-any.whl (324 kB)
Collecting ydata-profiling
  Using cached ydata_profiling-4.6.4-py2.py3-none-any.whl (357 kB)
Collecting pydantic>=2
  Using cached pydantic-2.5.3-py3-none-any.whl (381 kB)
Requirement already satisfied: numpy<1.26,>=1.16.0 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (1.24.4)
Requirement already satisfied: requests<3,>=2.24.0 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (2.28.1)
Collecting numba<0.59.0,>=0.56.0
  Using cached numba-0.58.1-cp39-cp39-win_amd64.whl (2.6 MB)
Collecting typeguard<5,>=4.1.2
  Using cached typeguard-4.1.5-py3-none-any.whl (34 kB)
Requirement already satisfied: seaborn<0.13,>=0.10.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (0.11.2)
Requirement already satisfied: tqdm<5,>=4.48.2 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (4.64.1)
Requirement already satisfied: jinja2<3.2,>=2.11.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (2.11.3)
Requirement already satisfied: htmlmin==0.1.12 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (0.1.12)
Requirement already satisfied: pandas!=1.4.0,<3,>1.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (1.4.4)
Collecting wordcloud>=1.9.1
  Using cached wordcloud-1.9.3-cp39-cp39-win_amd64.whl (300 kB)
Requirement already satisfied: statsmodels<1,>=0.13.2 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (0.13.5)
Collecting imagehash==4.3.1
  Using cached ImageHash-4.3.1-py2.py3-none-any.whl (296 kB)
Collecting phik<0.13,>=0.11.1
  Using cached phik-0.12.4-cp39-cp39-win_amd64.whl (666 kB)
Collecting multimethod<2,>=1.4
  Using cached multimethod-1.10-py3-none-any.whl (9.9 kB)
Requirement already satisfied: scipy<1.12,>=1.4.1 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (1.9.1)
Requirement already satisfied: PyYAML<6.1,>=5.0.0 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (6.0)
Requirement already satisfied: matplotlib<3.9,>=3.2 in c:\users\imikemori\anaconda3\lib\site-packages (from ydata-profiling->pandas_profiling) (3.5.2)
Collecting visions[type_image_path]==0.7.5
  Using cached visions-0.7.5-py3-none-any.whl (102 kB)
Collecting dacite>=1.8
  Using cached dacite-1.8.1-py3-none-any.whl (14 kB)
Requirement already satisfied: PyWavelets in c:\users\imikemori\anaconda3\lib\site-packages (from imagehash==4.3.1->ydata-profiling->pandas_profiling) (1.3.0)
Requirement already satisfied: pillow in c:\users\imikemori\anaconda3\lib\site-packages (from imagehash==4.3.1->ydata-profiling->pandas_profiling) (9.2.0)
Requirement already satisfied: tangled-up-in-unicode>=0.0.4 in c:\users\imikemori\anaconda3\lib\site-packages (from visions[type_image_path]==0.7.5->ydata-profiling->pandas_profiling) (0.2.0)
Requirement already satisfied: networkx>=2.4 in c:\users\imikemori\anaconda3\lib\site-packages (from visions[type_image_path]==0.7.5->ydata-profiling->pandas_profiling) (2.8.4)
Requirement already satisfied: attrs>=19.3.0 in c:\users\imikemori\anaconda3\lib\site-packages (from visions[type_image_path]==0.7.5->ydata-profiling->pandas_profiling) (21.4.0)
Requirement already satisfied: MarkupSafe>=0.23 in c:\users\imikemori\anaconda3\lib\site-packages (from jinja2<3.2,>=2.11.1->ydata-profiling->pandas_profiling) (2.0.1)
Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (1.4.2)
Requirement already satisfied: python-dateutil>=2.7 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (2.8.2)
Requirement already satisfied: fonttools>=4.22.0 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (4.25.0)
Requirement already satisfied: pyparsing>=2.2.1 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (3.0.9)
Requirement already satisfied: cycler>=0.10 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (0.11.0)
Requirement already satisfied: packaging>=20.0 in c:\users\imikemori\anaconda3\lib\site-packages (from matplotlib<3.9,>=3.2->ydata-profiling->pandas_profiling) (21.3)
Collecting llvmlite<0.42,>=0.41.0dev0
  Using cached llvmlite-0.41.1-cp39-cp39-win_amd64.whl (28.1 MB)
Requirement already satisfied: pytz>=2020.1 in c:\users\imikemori\anaconda3\lib\site-packages (from pandas!=1.4.0,<3,>1.1->ydata-profiling->pandas_profiling) (2022.1)
Requirement already satisfied: joblib>=0.14.1 in c:\users\imikemori\anaconda3\lib\site-packages (from phik<0.13,>=0.11.1->ydata-profiling->pandas_profiling) (1.1.0)
Collecting annotated-types>=0.4.0
  Using cached annotated_types-0.6.0-py3-none-any.whl (12 kB)
Requirement already satisfied: typing-extensions>=4.6.1 in c:\users\imikemori\anaconda3\lib\site-packages (from pydantic>=2->ydata-profiling->pandas_profiling) (4.9.0)
Collecting pydantic-core==2.14.6
  Using cached pydantic_core-2.14.6-cp39-none-win_amd64.whl (1.9 MB)
Requirement already satisfied: charset-normalizer<3,>=2 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (2.0.4)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (2022.9.14)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (1.26.11)
Requirement already satisfied: idna<4,>=2.5 in c:\users\imikemori\anaconda3\lib\site-packages (from requests<3,>=2.24.0->ydata-profiling->pandas_profiling) (3.3)
Requirement already satisfied: patsy>=0.5.2 in c:\users\imikemori\anaconda3\lib\site-packages (from statsmodels<1,>=0.13.2->ydata-profiling->pandas_profiling) (0.5.2)
Requirement already satisfied: colorama in c:\users\imikemori\anaconda3\lib\site-packages (from tqdm<5,>=4.48.2->ydata-profiling->pandas_profiling) (0.4.5)
Requirement already satisfied: importlib-metadata>=3.6 in c:\users\imikemori\anaconda3\lib\site-packages (from typeguard<5,>=4.1.2->ydata-profiling->pandas_profiling) (4.11.3)
Requirement already satisfied: zipp>=0.5 in c:\users\imikemori\anaconda3\lib\site-packages (from importlib-metadata>=3.6->typeguard<5,>=4.1.2->ydata-profiling->pandas_profiling) (3.8.0)
Requirement already satisfied: six in c:\users\imikemori\anaconda3\lib\site-packages (from patsy>=0.5.2->statsmodels<1,>=0.13.2->ydata-profiling->pandas_profiling) (1.16.0)
Installing collected packages: pydantic-core, multimethod, llvmlite, dacite, annotated-types, typeguard, pydantic, numba, imagehash, wordcloud, visions, phik, ydata-profiling, pandas_profiling
  Attempting uninstall: llvmlite
    Found existing installation: llvmlite 0.38.0
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
    WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
    WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
ERROR: Cannot uninstall 'llvmlite'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall.
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
import pandas_profiling
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_13352\1591302161.py in <module>
----> 1 import pandas_profiling

ModuleNotFoundError: No module named 'pandas_profiling'
pip show pandas_profiling
Note: you may need to restart the kernel to use updated packages.
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Package(s) not found: pandas_profiling
pandas_profiling.ProfileReport(dados)

Exemplo: pacote sweetviz#

# Instale se necessário ou se for executar no Google colab
!pip install sweetviz==2.1.1
!pip show sweetviz
#import sweetviz library
import sweetviz as xx

#analisando os dados do banco
study_report = xx.analyze(dados)

# Gerar relatório
study_report.show_html('Dados_banco.html')

Prática#

Visualização e exploração de dados#

Associação entre variáveis#

Considere os dados de 10 mil clientes de um banco no arquivo dados_banco.csv. Estão disponíveis as variáveis:

  • Cliente: Identificador do cliente.

  • Sexo: Feminino (F) ou Masculino (M)

  • Idade: Idade do cliente, em anos completos.

  • Empresa: Tipo da empresa em que trabalha: Pública, Privada ou Autônomo

  • Salário: Salário declarado pelo cliente na abertura da conta, em reais.

  • Saldo_cc: Saldo em conta corrente, em reais.

  • Saldo_poupança: Saldo em poupança, em reais.

  • Saldo_investimento: Saldo em investimentos, em reais.

  • Devedor_cartao: Valor em atraso no cartão de crédito, em reais.

  • Inadimplente: Se o cliente é considerado inadimplente atualmente (1) ou não (0), de acordo com critérios preestabelecidos.

Analise as possíveis associações entre o sexo, idade, empresa, salário, saldo em conta corrente, saldo em conta poupança, saldo em investimento e devedor no cartão com a variável Inadimplente. Teste outras combinações, por exemplo incluindo a empresa nos gráficos anteriores. Adicione novos elementos utilizando a ajuda das funções. Interprete os resultados.

import os.path
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy import stats

%matplotlib inline

#pkgdir = '/hdd/MBA/ECD/Data'
#Dados banco - Leitura dos dados
#dados = pd.read_csv(f'{pkgdir}/dados_banco.csv', index_col=0)

dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)

dados.head()
Sexo Idade Empresa Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
Cliente
75928 M 32 Privada 5719.00 933.79 0.0 0.0 6023.68 0
52921 F 28 Privada 5064.00 628.37 0.0 0.0 1578.24 0
8387 F 24 Autônomo 4739.00 889.18 0.0 0.0 2578.70 0
54522 M 30 Pública 5215.00 1141.47 0.0 0.0 4348.96 0
45397 M 30 Autônomo 5215.56 520.70 0.0 0.0 1516.78 1

Associação entre sexo e inadimplência#

# Tabela de dupla entrada

tabela_dupla = pd.crosstab(index=dados['Inadimplente'], columns=dados['Sexo'])

tabela_dupla
Sexo F M
Inadimplente
0 3185 4354
1 1046 1415
tabela_dupla/tabela_dupla.sum()
Sexo F M
Inadimplente
0 0.752777 0.754724
1 0.247223 0.245276

Associação entre idade e inadimplência#

import seaborn as sns
sns.boxplot(y=dados["Idade"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Idade'>
../_images/20bd13ed746e3cf82a087dafc993f81d8de61e63ca77263cacf8ad42db74a8f5.png

Associação entre empresa e inadimplência#

# Tabela de dupla entrada

tabela_dupla = pd.crosstab(index=dados['Inadimplente'], columns=dados['Empresa'], margins=True, margins_name='Total')

tabela_dupla
Empresa Autônomo Privada Pública Total
Inadimplente
0 832 4579 2128 7539
1 615 1524 322 2461
Total 1447 6103 2450 10000
tabela_dupla/tabela_dupla.sum()
Empresa Autônomo Privada Pública Total
Inadimplente
0 0.287491 0.375143 0.434286 0.37695
1 0.212509 0.124857 0.065714 0.12305
Total 0.500000 0.500000 0.500000 0.50000
# Tabela de dupla entrada

tabela_dupla = pd.crosstab(index=dados['Inadimplente'], columns=dados['Empresa'], margins=False, margins_name='Total')

tabela_dupla
Empresa Autônomo Privada Pública
Inadimplente
0 832 4579 2128
1 615 1524 322
tabela_dupla.plot.bar(stacked=True)

plt.legend(title='Empresa')

plt.show()
../_images/a728e0bb704ed54f1f2f11fc5e31d6406c328b2b7a2cbb33c8b4b12cba5c40ac.png

Associação entre salário e inadimplência#

import seaborn as sns
sns.boxplot(y=dados["Salario"],x=dados["Inadimplente"], palette='Set2')
<Axes: xlabel='Inadimplente', ylabel='Salario'>
../_images/40549b5b7d3612eb84e12b0af4dd0c57f2dd2dde9adbc39cb5cf44509f1c1bcf.png

Associação entre saldo em conta corrente e inadimplência#

import seaborn as sns
sns.boxplot(y=dados["Saldo_cc"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Saldo_cc'>
../_images/8147933a13eccd18057d7d2bf8ea1f23b664cd837a4856677820a376afa4e260.png

Associação entre saldo em conta poupança e inadimplência#

import seaborn as sns
sns.boxplot(y=dados["Saldo_poupança"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Saldo_poupança'>
../_images/a3a914fa7ad045c793a1c1900f9883d427281ed14ef28a9a9692fe582adba28d.png

Associação entre devedor no cartão e inadimplência#

import seaborn as sns
sns.boxplot(y=dados["Devedor_cartao"],x=dados["Inadimplente"], palette="Set2")
<Axes: xlabel='Inadimplente', ylabel='Devedor_cartao'>
../_images/80d04d911c1ddc1c7ed3729c90bf0e21838356b9b6b15cd88a84e263a305f7a3.png
import seaborn as sns
sns.boxplot(y=dados["Devedor_cartao"],x=dados["Inadimplente"], hue=dados["Empresa"], palette="Blues")
<Axes: xlabel='Inadimplente', ylabel='Devedor_cartao'>
../_images/1f4efcd5609eac355c890026ff5fae0cecba0ad9965ba6c56bf1d0d74dcf92c4.png
import seaborn as sns
sns.catplot(y="Devedor_cartao",x="Inadimplente", hue='Empresa', data=dados, kind='boxen', palette="Set2")
<seaborn.axisgrid.FacetGrid at 0x7f4dad8edc10>
../_images/367500eeb86b1e3bfa30b992300c083ae19f3f91d5cac422bf75d41f2e5404fd.png
import seaborn as sns
sns.catplot(y="Devedor_cartao",x="Inadimplente", hue='Empresa', data=dados, kind='strip', palette="Set2")
<seaborn.axisgrid.FacetGrid at 0x7f4dad8e6d00>
../_images/be165ad5ea501889e04296a7a40346d35e234428f1bb8866242c22b51ec40adb.png
sns.displot(dados, x='Devedor_cartao', col='Sexo', hue='Inadimplente', bins=30, palette='Set2');
../_images/c917074ce1362affe48afbea419b34102b97d52eae70f8ea52974ab111a3267f.png

Como não fizemos análises inferenciais, não podemos concluir estatisticamente sobre as associações. Porém, com base nas análises de visualização e exploração de dados, parece existir associação entre saldo em conta corrente e inadimplência e tipo de empresa. A distribuição de dados no devedor de cartão parece ser distinta para os grupos definidos pela variável inadimplente.

Visualizando dados da Covid19

1. Coletando e lendo dados Covid19#

Antes de tudo, instale a biblioteca plotly:

# Para instalar o plotly descomente e execute o comando abaixo
# !pip install plotly

Vamos usar dados do covid19 fornecidos pela “Our World in Data”:

import pandas as pd
import numpy as np
import plotly.express as px
 

# Conjunto de dados reais da covid19

# Reporitorio dos dados:
# https://github.com/owid/covid-19-data/tree/master/public/data
# https://ourworldindata.org/coronavirus-source-data

df = pd.read_csv('covid-data.csv')
df
iso_code continent location date total_cases new_cases new_cases_smoothed total_deaths new_deaths new_deaths_smoothed ... gdp_per_capita extreme_poverty cardiovasc_death_rate diabetes_prevalence female_smokers male_smokers handwashing_facilities hospital_beds_per_thousand life_expectancy human_development_index
0 AFG Asia Afghanistan 2020-02-24 1.0 1.0 NaN NaN NaN NaN ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
1 AFG Asia Afghanistan 2020-02-25 1.0 0.0 NaN NaN NaN NaN ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
2 AFG Asia Afghanistan 2020-02-26 1.0 0.0 NaN NaN NaN NaN ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
3 AFG Asia Afghanistan 2020-02-27 1.0 0.0 NaN NaN NaN NaN ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
4 AFG Asia Afghanistan 2020-02-28 1.0 0.0 NaN NaN NaN NaN ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
82832 ZWE Africa Zimbabwe 2021-04-15 37422.0 53.0 52.857 1550.0 2.0 2.571 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82833 ZWE Africa Zimbabwe 2021-04-16 37534.0 112.0 55.286 1551.0 1.0 2.286 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82834 ZWE Africa Zimbabwe 2021-04-17 37699.0 165.0 60.857 1552.0 1.0 2.000 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82835 ZWE Africa Zimbabwe 2021-04-18 37751.0 52.0 66.143 1553.0 1.0 2.143 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82836 ZWE Africa Zimbabwe 2021-04-19 37859.0 108.0 78.857 1553.0 0.0 1.571 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571

82837 rows × 59 columns

Não se pode analizar os dados sem entender o que são as variáveis (features/atributos). Temos um codebook pra entender como os ciêntistas codificaram cada variável:

# O que sao cada atributo
# https://github.com/owid/covid-19-data/blob/master/public/data/owid-covid-codebook.csv
df_codebook = pd.read_csv('covid-codebook.csv')
df_codebook.head(10)
column source description
0 iso_code International Organization for Standardization ISO 3166-1 alpha-3 – three-letter country codes
1 continent Our World in Data Continent of the geographical location
2 location Our World in Data Geographical location
3 date Our World in Data Date of observation
4 total_cases COVID-19 Data Repository by the Center for Sys... Total confirmed cases of COVID-19
5 new_cases COVID-19 Data Repository by the Center for Sys... New confirmed cases of COVID-19
6 new_cases_smoothed COVID-19 Data Repository by the Center for Sys... New confirmed cases of COVID-19 (7-day smoothed)
7 total_deaths COVID-19 Data Repository by the Center for Sys... Total deaths attributed to COVID-19
8 new_deaths COVID-19 Data Repository by the Center for Sys... New deaths attributed to COVID-19
9 new_deaths_smoothed COVID-19 Data Repository by the Center for Sys... New deaths attributed to COVID-19 (7-day smoot...

2. Visualizando dados covid#

Vamos selecionar dados do mês 4 e criar um dataset para explorarmos:

df_aux = df[df['date'].str.startswith('2021-04')]
agg = {
    "total_cases": "median", "total_deaths": "median",
    "total_cases_per_million": "median", "total_deaths_per_million": "median",
    "population": "median", "population_density": "median", "median_age": "median",
    "gdp_per_capita": "median", "extreme_poverty": "median",
    "human_development_index": "median", "life_expectancy": "median",
    "cardiovasc_death_rate": "median", "diabetes_prevalence": "median", "handwashing_facilities": "median",
    "people_vaccinated": "median", "people_fully_vaccinated": "median",
    "people_vaccinated_per_hundred": "median", "people_vaccinated_per_hundred": "median"
}
df_clean = df_aux.groupby(["iso_code", "continent", "location"]).agg(agg).reset_index()
df_clean
iso_code continent location total_cases total_deaths total_cases_per_million total_deaths_per_million population population_density median_age gdp_per_capita extreme_poverty human_development_index life_expectancy cardiovasc_death_rate diabetes_prevalence handwashing_facilities people_vaccinated people_fully_vaccinated people_vaccinated_per_hundred
0 AFG Asia Afghanistan 57144.0 2521.0 1467.928 64.760 38928341.0 54.422 18.6 1803.987 NaN 0.511 64.83 597.029 9.59 37.746 120000.0 NaN 0.310
1 AGO Africa Angola 23331.0 550.0 709.877 16.734 32866268.0 23.890 16.8 5819.495 NaN 0.581 61.15 276.045 3.94 26.664 213510.0 NaN 0.650
2 AIA North America Anguilla NaN NaN NaN NaN 15002.0 NaN NaN NaN NaN NaN 81.88 NaN NaN NaN 5835.0 NaN 38.890
3 ALB Europe Albania 128155.0 2310.0 44532.282 802.697 2877800.0 104.871 38.0 11803.431 1.1 0.795 78.57 304.195 10.08 NaN NaN NaN NaN
4 AND Europe Andorra 12497.0 120.0 161742.057 1553.096 77265.0 163.755 NaN NaN NaN 0.868 83.73 109.135 7.97 NaN 9781.0 4484.0 12.660
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
202 WSM Oceania Samoa 3.0 NaN 15.120 NaN 198410.0 69.413 22.0 6021.557 NaN 0.715 73.32 348.977 9.21 NaN NaN NaN NaN
203 YEM Asia Yemen 5276.0 1031.0 176.893 34.567 29825968.0 53.508 20.3 1479.147 18.8 0.470 66.12 495.003 5.35 49.542 NaN NaN NaN
204 ZAF Africa South Africa 1557527.0 53256.0 26261.362 897.946 59308690.0 46.754 27.3 12294.876 18.9 0.709 64.13 200.380 5.52 43.993 285998.5 285998.5 0.485
205 ZMB Africa Zambia 89918.0 1226.0 4891.113 66.689 18383956.0 22.995 17.7 3689.251 57.5 0.584 63.89 234.499 3.94 13.938 106.0 NaN 0.000
206 ZWE Africa Zimbabwe 37273.0 1538.0 2507.783 103.479 14862927.0 42.729 19.6 1899.775 21.4 0.571 61.49 307.846 1.82 36.791 186086.5 28382.5 1.250

207 rows × 20 columns

Como conjunto de dados é real, há muitos valores faltantes. Vamos tratar de forma simples as variáveis que vamos trabalhar:

df_clean = df_clean[df_clean['continent'].notna()]
df_clean = df_clean[df_clean['total_cases'].notna()]
df_clean = df_clean[df_clean['total_cases_per_million'].notna()]
df_clean = df_clean[df_clean['total_deaths_per_million'].notna()]
df_clean["people_fully_vaccinated"] = df_clean["people_fully_vaccinated"].fillna(0.0)
df_clean
iso_code continent location total_cases total_deaths total_cases_per_million total_deaths_per_million population population_density median_age gdp_per_capita extreme_poverty human_development_index life_expectancy cardiovasc_death_rate diabetes_prevalence handwashing_facilities people_vaccinated people_fully_vaccinated people_vaccinated_per_hundred
0 AFG Asia Afghanistan 57144.0 2521.0 1467.928 64.760 38928341.0 54.422 18.6 1803.987 NaN 0.511 64.83 597.029 9.59 37.746 120000.0 0.0 0.310
1 AGO Africa Angola 23331.0 550.0 709.877 16.734 32866268.0 23.890 16.8 5819.495 NaN 0.581 61.15 276.045 3.94 26.664 213510.0 0.0 0.650
3 ALB Europe Albania 128155.0 2310.0 44532.282 802.697 2877800.0 104.871 38.0 11803.431 1.1 0.795 78.57 304.195 10.08 NaN NaN 0.0 NaN
4 AND Europe Andorra 12497.0 120.0 161742.057 1553.096 77265.0 163.755 NaN NaN NaN 0.868 83.73 109.135 7.97 NaN 9781.0 4484.0 12.660
5 ARE Asia United Arab Emirates 481937.0 1529.0 48727.756 154.594 9890400.0 112.442 34.0 67293.483 NaN 0.890 77.97 317.840 17.26 NaN NaN 0.0 NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
200 VNM Asia Vietnam 2692.0 35.0 27.656 0.360 97338583.0 308.127 32.6 6171.884 2.0 0.704 75.40 245.465 6.00 85.847 58248.0 0.0 0.060
203 YEM Asia Yemen 5276.0 1031.0 176.893 34.567 29825968.0 53.508 20.3 1479.147 18.8 0.470 66.12 495.003 5.35 49.542 NaN 0.0 NaN
204 ZAF Africa South Africa 1557527.0 53256.0 26261.362 897.946 59308690.0 46.754 27.3 12294.876 18.9 0.709 64.13 200.380 5.52 43.993 285998.5 285998.5 0.485
205 ZMB Africa Zambia 89918.0 1226.0 4891.113 66.689 18383956.0 22.995 17.7 3689.251 57.5 0.584 63.89 234.499 3.94 13.938 106.0 0.0 0.000
206 ZWE Africa Zimbabwe 37273.0 1538.0 2507.783 103.479 14862927.0 42.729 19.6 1899.775 21.4 0.571 61.49 307.846 1.82 36.791 186086.5 28382.5 1.250

181 rows × 20 columns

Vamos criar um “scatter plot” para visualizar algumas de nossas variáves quantitativas (quantitativa x quantitativa):

fig = px.scatter_matrix(
    df_clean,
    dimensions=["life_expectancy", "human_development_index", "median_age", "total_deaths_per_million"],
    color="total_deaths_per_million",
    hover_data=["location"],
#     color_continuous_scale=px.colors.sequential.Reds
    color_continuous_scale=px.colors.diverging.BrBG
)
fig.show()

Vamos visualizar a distribuição e boxplot da variável “total_deaths_per_million”, por meio do violin:

fig = px.violin(
    df_clean, y="total_deaths_per_million",
    box=True, # draw box plot inside the violin
    hover_data=["location"],
    points='all', # can be 'outliers', or False
)
fig.show()

Vamos visualizar violins da variável “life_expectancy” (quantitativa) junto com “continent” (qualitativa):

fig = px.violin(
    df_clean, y="life_expectancy", x="continent",
#     df_clean, y="population", x="continent",
    box=True, # draw box plot inside the violin
    hover_data=["location"],
    points='all', # can be 'outliers', or False
)
fig.show()
df_clean["continent"].unique()
array(['Asia', 'Africa', 'Europe', 'South America', 'North America',
       'Oceania'], dtype=object)

Gráfico de pizza para visualizar “population” (quantitativa) e “continent” (qualitativa):

fig = px.pie(
    df_clean,
    values='total_cases',
    names='continent',
#     hover_data=["life_expectancy", "human_development_index", "median_age", "total_deaths_per_million"]
)
fig.show()

Vamos visualizar o total de casos por milhão de habitantes em cada pais/continente:

df_clean["iso_code"]
0      AFG
1      AGO
3      ALB
4      AND
5      ARE
      ... 
200    VNM
203    YEM
204    ZAF
205    ZMB
206    ZWE
Name: iso_code, Length: 181, dtype: object
fig = px.scatter_geo(
    df_clean,
    locations="iso_code",
    color="continent",
    hover_name="location",
    hover_data=["people_fully_vaccinated", "population"],
    size="total_cases_per_million",
    scope="world",
    projection="natural earth",
    size_max=20
)
fig.show()
fig = px.choropleth(
    df_clean,
    locations="iso_code",
    color="total_cases_per_million",
    hover_data=["people_fully_vaccinated", "population", "location"],
    projection="natural earth",
    color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_log10 = df_clean
df_clean_log10["total_cases_per_million_log10"] = np.log10(df_clean_log10["total_cases_per_million"])

fig = px.sunburst(
    df_clean_log10,
    path=['continent', 'location'],
    values='population',
    color='total_cases_per_million_log10',
    hover_data=['iso_code'],
#     color_continuous_scale=px.colors.sequential.Viridis,
    color_continuous_scale=px.colors.diverging.BrBG
)
fig.show()
df_clean_sort = df_clean.sort_values("total_cases_per_million", ascending=False)

fig = px.bar(
    df_clean_sort,
    x='location',
    y='total_cases_per_million',
    hover_data=['total_cases_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()

Vamos visualizar o total de morte por milhão de habitantes em cada pais/continente:

fig = px.choropleth(
    df_clean,
    locations="iso_code",
    color="total_deaths_per_million",
    hover_data=["people_fully_vaccinated", "population"],
    projection="natural earth",
    color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_log10 = df_clean
df_clean_log10["total_deaths_per_million_log10"] = np.log10(df_clean["total_deaths_per_million"])

fig = px.sunburst(
    df_clean_log10,
    path=['continent', 'location'],
    values='population',
    color='total_deaths_per_million_log10',
    hover_data=['iso_code'],
#     color_continuous_scale=px.colors.sequential.Viridis,
    color_continuous_scale=px.colors.diverging.BrBG
)
fig.show()
fig = px.treemap(
    df_clean_log10,
    path=[px.Constant('world'), 'continent', 'location'],
    values='population',
    color='total_deaths_per_million_log10', hover_data=['iso_code'],
    color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_sort = df_clean.sort_values("total_deaths_per_million", ascending=False)
fig = px.bar(
    df_clean_sort,
    x='location',
    y='total_deaths_per_million',
    hover_data=['total_cases_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()

Agora vamos olhar só para a América do Sul e visualizar o total de casos/morte por milhão de habitantes em cada pais:

df_clean_sa = df_clean[df_clean["continent"] == "South America"]
fig = px.choropleth(
    df_clean_sa,
    locations="iso_code",
    color="total_cases_per_million",
    hover_name="location",
    scope="south america",
    color_continuous_scale=px.colors.sequential.Reds
)
fig.show()
df_clean_sa_sort = df_clean_sa.sort_values("total_cases_per_million", ascending=False)
fig = px.bar(
    df_clean_sa_sort,
    x='location',
    y='total_cases_per_million',
    hover_data=['total_cases_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()
fig = px.choropleth(df_clean_sa, locations="iso_code", color="total_deaths_per_million",
                    hover_name="location", scope="south america",
                   color_continuous_scale=px.colors.sequential.Reds)
fig.show()
df_clean_sa_sort = df_clean_sa.sort_values("total_deaths_per_million", ascending=False)
fig = px.bar(
    df_clean_sa_sort,
    x='location',
    y='total_deaths_per_million',
    hover_data=['total_deaths_per_million', 'population', 'people_fully_vaccinated']
)
fig.show()

3. Visualizando dados covid em multiplas janelas de tempo#

Vamos visualizar no tempo o numero de mortes/casos:

dfm = df
dfm = dfm[dfm['continent'].notna()]
dfm = dfm[dfm['total_cases'].notna()]
dfm = dfm[dfm['total_cases_per_million'].notna()]
dfm = dfm[dfm['total_deaths_per_million'].notna()]

dfm
iso_code continent location date total_cases new_cases new_cases_smoothed total_deaths new_deaths new_deaths_smoothed ... gdp_per_capita extreme_poverty cardiovasc_death_rate diabetes_prevalence female_smokers male_smokers handwashing_facilities hospital_beds_per_thousand life_expectancy human_development_index
27 AFG Asia Afghanistan 2020-03-22 34.0 4.0 2.571 1.0 1.0 0.143 ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
28 AFG Asia Afghanistan 2020-03-23 41.0 7.0 3.286 1.0 0.0 0.143 ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
29 AFG Asia Afghanistan 2020-03-24 43.0 2.0 3.286 1.0 0.0 0.143 ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
30 AFG Asia Afghanistan 2020-03-25 76.0 33.0 7.429 2.0 1.0 0.286 ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
31 AFG Asia Afghanistan 2020-03-26 80.0 4.0 7.857 3.0 1.0 0.429 ... 1803.987 NaN 597.029 9.59 NaN NaN 37.746 0.5 64.83 0.511
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
82832 ZWE Africa Zimbabwe 2021-04-15 37422.0 53.0 52.857 1550.0 2.0 2.571 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82833 ZWE Africa Zimbabwe 2021-04-16 37534.0 112.0 55.286 1551.0 1.0 2.286 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82834 ZWE Africa Zimbabwe 2021-04-17 37699.0 165.0 60.857 1552.0 1.0 2.000 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82835 ZWE Africa Zimbabwe 2021-04-18 37751.0 52.0 66.143 1553.0 1.0 2.143 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571
82836 ZWE Africa Zimbabwe 2021-04-19 37859.0 108.0 78.857 1553.0 0.0 1.571 ... 1899.775 21.4 307.846 1.82 1.6 30.7 36.791 1.7 61.49 0.571

67467 rows × 59 columns

Vamos visualizar “new_cases_per_million”. Para isso inicialmente vamos criar janelas mensais e pegar os valores medianos das variáveis analisadas:

df_ncm = dfm
months = df_ncm['date'].apply(lambda x: x[:7])
df_ncm['months'] = months
df_ncm = df_ncm.groupby(['iso_code', "location", 'months', "continent"]).agg({"new_cases_per_million": "median"})
df_ncm
new_cases_per_million
iso_code location months continent
AFG Afghanistan 2020-03 Asia 0.2830
2020-04 Asia 1.4390
2020-05 Asia 10.4810
2020-06 Asia 14.3855
2020-07 Asia 3.9560
... ... ... ... ...
ZWE Zimbabwe 2020-12 Africa 7.5360
2021-01 Africa 42.9930
2021-02 Africa 5.0795
2021-03 Africa 1.7490
2021-04 Africa 2.1530

2379 rows × 1 columns

df_ncm = df_ncm.reset_index()
df_ncm = df_ncm[df_ncm["months"] >= "2020-03"] # primeiro mes (2020-01) tem alguns problemas, entao vamos remover

fig = px.scatter_geo(
    df_ncm,
    locations="iso_code",
    color="continent",
    hover_name="location",
    size="new_cases_per_million",
    animation_frame="months",
    scope="world",
    projection="natural earth",
    size_max=30
)

fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 2000
fig.show()

Vamos visualizar “new_deaths_per_million”. Para isso inicialmente vamos criar janelas mensais e pegar os valores medianos das variáveis analisadas:

df_ncm = dfm
months = df_ncm['date'].apply(lambda x: x[:7])
df_ncm['months'] = months
df_ncm = df_ncm.groupby(['iso_code', "location", 'months', "continent"]).agg({"new_deaths_per_million": "median"})
df_ncm
new_deaths_per_million
iso_code location months continent
AFG Afghanistan 2020-03 Asia 0.0000
2020-04 Asia 0.0385
2020-05 Asia 0.1280
2020-06 Asia 0.3340
2020-07 Asia 0.4370
... ... ... ... ...
ZWE Zimbabwe 2020-12 Africa 0.1350
2021-01 Africa 1.6150
2021-02 Africa 0.5045
2021-03 Africa 0.1350
2021-04 Africa 0.0670

2379 rows × 1 columns

df_ncm = df_ncm.reset_index()
df_ncm = df_ncm[df_ncm["months"] >= "2020-03"] # primeiro mes (2020-01) tem alguns problemas, entao vamos remover

fig = px.scatter_geo(
    df_ncm,
    locations="iso_code",
    color="continent",
    hover_name="location",
    size="new_deaths_per_million",
    animation_frame="months",
    scope="world",
    projection="natural earth",
    size_max=30
)

fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 2000

fig.show()

Para a próxima análise vamos visualizar a variável “new_cases_smoothed_per_million” (média movel semanal). A idéia é contruir gráficos de linhas e visualizar o que aontece ao longo do tempo nos países da América do Sul:

dfm_aux = dfm[dfm["continent"] == "South America"]

dfm_aux = dfm_aux[
    ["date", "location", "new_deaths_smoothed_per_million", "new_cases_smoothed_per_million"]]
# dfm_aux = dfm_aux.set_index("date")
dfm_aux
date location new_deaths_smoothed_per_million new_cases_smoothed_per_million
3041 2020-03-08 Argentina 0.003 0.038
3042 2020-03-09 Argentina 0.003 0.038
3043 2020-03-10 Argentina 0.003 0.051
3044 2020-03-11 Argentina 0.003 0.057
3045 2020-03-12 Argentina 0.003 0.057
... ... ... ... ...
80756 2021-04-15 Venezuela 0.668 40.150
80757 2021-04-16 Venezuela 0.658 40.934
80758 2021-04-17 Venezuela 0.648 40.778
80759 2021-04-18 Venezuela 0.638 41.713
80760 2021-04-19 Venezuela 0.653 44.124

4738 rows × 4 columns

fig = px.line(
    dfm_aux,
    x = "date",
    y = "new_cases_smoothed_per_million",
    facet_col="location",
    facet_col_wrap=4  
)

fig.show()

A mesma coisa, porém para “new_cases_smoothed_per_million” (média movel semanal):

fig = px.line(
    dfm_aux,
    x = "date",
    y = "new_deaths_smoothed_per_million",
    facet_col="location",
    facet_col_wrap=4  
)

fig.show()

Vamos agora visualizar no tempo ambas variáves “new_deaths_smoothed_per_million” e “new_cases_smoothed_per_million”

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=2,
    cols=1,
    shared_xaxes=True,
    vertical_spacing=0.02
)

dfm_aux_br = dfm_aux[dfm_aux["location"] == "Brazil"]

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_deaths_smoothed_per_million"],
        mode='lines',
        name='new_deaths_smoothed_per_million'
    ),
    row=1, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_cases_smoothed_per_million"],
        mode='lines',
        name='new_cases_smoothed_per_million'
    ),
    row=2, col=1
)

fig.update_layout()
fig.show()

4. Visualizando vacinação#

Vamos agora analisar dois paises (Israel e United States) e verificar efeito da vacinação nos numeros de morte e novos casos.

dfm_aux_ = df_aux

s1 = dfm_aux_["people_vaccinated"]/dfm_aux_["population"]
s1.name = "percpop_vaccinated"

dfm_aux_ = pd.concat([dfm_aux_, s1], axis=1)
dfm_aux_ = dfm_aux_.groupby(["location"]).agg({"percpop_vaccinated": "max"}).reset_index()
dfm_aux_.fillna(0.0, inplace=True)

dfm_aux_.sort_values("percpop_vaccinated", ascending=False, inplace=True)
dfm_aux_.head(20)
location percpop_vaccinated
76 Gibraltar 1.065596
67 Falkland Islands 0.755670
173 Seychelles 0.673683
23 Bhutan 0.620622
97 Israel 0.618871
96 Isle of Man 0.590260
121 Maldives 0.521417
36 Cayman Islands 0.514425
204 United Kingdom 0.485114
101 Jersey 0.471669
81 Guernsey 0.470351
123 Malta 0.433898
22 Bermuda 0.414000
39 Chile 0.404639
205 United States 0.399760
168 San Marino 0.396606
6 Anguilla 0.388948
15 Bahrain 0.360155
88 Hungary 0.343368
48 Curacao 0.336082

Análise para “United States”:

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=5,
    cols=1,
    shared_xaxes=True,
    vertical_spacing=0.02
)

dfm_aux = dfm[dfm["location"] == "United States"]
dfm_aux_br = dfm_aux[
    ["date", "location", "new_deaths_smoothed_per_million",
     "new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
     "population", "people_vaccinated", "people_fully_vaccinated"]]

s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"

s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"

dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)

# dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
# dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_deaths_smoothed_per_million"],
        mode='lines',
        name='new_deaths_smoothed_per_million'
    ),
    row=1, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_cases_smoothed_per_million"],
        mode='lines',
        name='new_cases_smoothed_per_million'
    ),
    row=2, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
        mode='lines',
        name='new_vaccinations_smoothed_per_million'
    ),
    row=3, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_vaccinated"],
        mode='lines',
        name='percpop_vaccinated'
    ),
    row=4, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_fully_vaccinated"],
        mode='lines',
        name='percpop_fully_vaccinated'
    ),
    row=5, col=1
)


fig.update_layout()
fig.show()

Análise para “United Kingdom”:

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=5,
    cols=1,
    shared_xaxes=True,
    vertical_spacing=0.02
)

dfm_aux = dfm[dfm["location"] == "United Kingdom"]
dfm_aux_br = dfm_aux[
    ["date", "location", "new_deaths_smoothed_per_million",
     "new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
     "population", "people_vaccinated", "people_fully_vaccinated"]]

s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"

s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"

dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)

# dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
# dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_deaths_smoothed_per_million"],
        mode='lines',
        name='new_deaths_smoothed_per_million'
    ),
    row=1, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_cases_smoothed_per_million"],
        mode='lines',
        name='new_cases_smoothed_per_million'
    ),
    row=2, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
        mode='lines',
        name='new_vaccinations_smoothed_per_million'
    ),
    row=3, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_vaccinated"],
        mode='lines',
        name='percpop_vaccinated'
    ),
    row=4, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_fully_vaccinated"],
        mode='lines',
        name='percpop_fully_vaccinated'
    ),
    row=5, col=1
)


fig.update_layout()
fig.show()

Análise para “Israel”:

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=5,
    cols=1,
    shared_xaxes=True,
    vertical_spacing=0.02
)

dfm_aux = dfm[dfm["location"] == "Israel"]
dfm_aux_br = dfm_aux[
    ["date", "location", "new_deaths_smoothed_per_million",
     "new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
     "population", "people_vaccinated", "people_fully_vaccinated"]]

s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"

s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"

dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)

dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_deaths_smoothed_per_million"],
        mode='lines',
        name='new_deaths_smoothed_per_million'
    ),
    row=1, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_cases_smoothed_per_million"],
        mode='lines',
        name='new_cases_smoothed_per_million'
    ),
    row=2, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
        mode='lines',
        name='new_vaccinations_smoothed_per_million'
    ),
    row=3, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_vaccinated"],
        mode='lines',
        name='percpop_vaccinated'
    ),
    row=4, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_fully_vaccinated"],
        mode='lines',
        name='percpop_fully_vaccinated'
    ),
    row=5, col=1
)


fig.update_layout()
fig.show()

E como estão as coisas no Brasil? :

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=5,
    cols=1,
    shared_xaxes=True,
    vertical_spacing=0.02
)

dfm_aux = dfm[dfm["location"] == "Brazil"]
dfm_aux_br = dfm_aux[
    ["date", "location", "new_deaths_smoothed_per_million",
     "new_cases_smoothed_per_million", "new_vaccinations_smoothed_per_million",
     "population", "people_vaccinated", "people_fully_vaccinated"]]

s1 = dfm_aux_br["people_vaccinated"]/dfm_aux_br["population"]
s1.name = "percpop_vaccinated"

s2 = dfm_aux_br["people_fully_vaccinated"]/dfm_aux_br["population"]
s2.name = "percpop_fully_vaccinated"

dfm_aux_br = pd.concat([dfm_aux_br, s1, s2], axis=1)

# dfm_aux_br["percpop_vaccinated"].fillna(0.0, inplace=True)
# dfm_aux_br["people_fully_vaccinated"].fillna(0.0, inplace=True)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_deaths_smoothed_per_million"],
        mode='lines',
        name='new_deaths_smoothed_per_million'
    ),
    row=1, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_cases_smoothed_per_million"],
        mode='lines',
        name='new_cases_smoothed_per_million'
    ),
    row=2, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["new_vaccinations_smoothed_per_million"],
        mode='lines',
        name='new_vaccinations_smoothed_per_million'
    ),
    row=3, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_vaccinated"],
        mode='lines',
        name='percpop_vaccinated'
    ),
    row=4, col=1
)

fig.add_trace(
    go.Scatter(
        x=dfm_aux_br["date"],
        y=dfm_aux_br["percpop_fully_vaccinated"],
        mode='lines',
        name='percpop_fully_vaccinated'
    ),
    row=5, col=1
)


fig.update_layout()
fig.show()

References#

Explore mais sobre Plotly:#

Aula 2: Probabilidades#

Vamos simular alguns problemas que vimos na aula.

Lançamento de uma moeda justa.

import random
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline


vp = [] # lista que armazena a fração de ocorrências em função do número de simulações
vsim = [] # armazena o número de simulações
Nmax = 1000 # numero maximo de simulacoes
moeda = ['C','R']
for nsim in np.arange(1,Nmax):
    nhead = 0 # numero de caras
    for i in range(1,nsim):
        face = random.choice(moeda)
        if(face == 'C'):
            nhead = nhead + 1 
    vp.append(nhead/nsim)
    vsim.append(nsim)

plt.figure(figsize=(8,6))
plt.plot(vsim, vp, linestyle='-', color="blue", linewidth=2,label = 'Valor simulado')
plt.axhline(y=1/2, color='r', linestyle='--', label = 'Valor teorico')
plt.ylabel("Fração de caras", fontsize=20)
plt.xlabel("Numero de experimentos", fontsize=20)
plt.xlim([0.0, Nmax])
plt.ylim([0.0, 1.0])
plt.legend()
plt.show(True) 
../_images/9432bae9ae65a2a1bb26381199dfbbdcce4a760b560d7c4a6373087bb4bd3032.png

Pela definição frequentista de probabilidades, vimos que a probabilidade de ocorrência de um evento \(A\) é definida por: $\( P(A) = \lim_{n\rightarrow \infty} \frac{n_A}{n}, \)\( onde \)n$ é o número de experimentos.

Exemplo: Lançando dois dados equilibrados, qual é a probabilidade de que:
a) A soma das faces seja igual a 7 (evento A).
a) Obter uma soma maior do que 5 (evento B).
Na aula, vimos que: $\( P(A) = 1/6 = 0.166 \)\( \)\( P(B) = 26/36 = 0.72. \)$

import random
import numpy as np
n = 1000 #numero de experimentos
nA = 0
nB = 0
faces = np.arange(1,7) #valores 1 a 6
for i in range(0,n):
    dado1 = random.choice(faces)
    dado2 = random.choice(faces)
    if (dado1+dado2 == 7):
        nA = nA + 1
    if((dado1 + dado2) > 5):
        nB = nB + 1
    
nA = nA/n
nB = nB/n
print('Probabilidade de que a soma das faces seja igual a 7:', nA)
print('Probabilidade de que a soma das faces seja maior do que 5:', nB)
Probabilidade de que a soma das faces seja igual a 7: 0.162
Probabilidade de que a soma das faces seja maior do que 5: 0.709

Exemplo: Qual é a probabilidade de que em um lançamento de dados saia um número par ou maior do que três?
Solução: 4/6 = 0.66.

import random
import numpy as np
n = 1000 #numero de experimentos
nA = 0
faces = np.arange(1,7) #valores 1 a 6
for i in range(0,n):
    dado = random.choice(faces)
    if (dado%2 == 0) or (dado > 3):
        nA = nA + 1
    
nA = nA/n
print('Probabilidade de que saia um número par ou maior do que três:', nA)
Probabilidade de que saia um número par ou maior do que três: 0.648

Exemplo: Uma caixa usada em um sorteio contém 5 bolas pretas numeradas de 1 a 5, sete bolas brancas numeradas de 1 a 7 e oito bolas vermelhas numeradas de 1 a 8.
i) Sorteando-se uma bola dessa caixa, qual é a probabilidade de encontrarmos uma bola preta? $\( P(A) = \frac{5}{20} = 0.25 \)$

import random
urna = ['P1','P2','P3','P4','P5','B1','B2','B3','B4','B5','B6','B7',
       'V1','V2','V3','V4','V5','V6','V7','V8'] 
n = 1000
nA = 0
for i in range(0,N):
    bola = random.choice(urna)
    if(bola[0][0] == 'P'):
        nA = nA + 1
print('P(A):', nA/n)
P(A): 0.262

ii) Sorteando-se uma bola preta, qual é a probabilidade de tal bola ser par? $\( P(B|A) = \frac{2/20}{5/20} = \frac{2}{5}= 0.4 \)$

import random
urna = ['P1','P2','P3','P4','P5','B1','B2','B3','B4','B5','B6','B7',
       'V1','V2','V3','V4','V5','V6','V7','V8'] 
n = 1000
nA = 0
nB = 0
for i in range(0,N):
    bola = random.choice(urna)
    if(bola[0] == 'P'):
        nA = nA + 1
        if(int(bola[1])%2 == 0):
            nB = nB + 1
print('P(B|A):', (nB/n)/(nA/n))
P(B|A): 0.41129032258064513

Exercícios de fixação#

Resolva e simule os exercícios a seguir.

1 - Lançando dois dados equilibrados, qual é a probabilidade de que:
a) A soma das faces seja igual a 6 (evento A). a) Obter uma soma maior do que 10 (evento B).

2 - Qual é a probabilidade de que em um lançamento de dados saia um número ímpar ou maior do que dois?

3 - Uma caixa usada em um sorteio contém 2 bolas pretas numeradas de 1 a 2, três bolas brancas numeradas de 1 a 3 e cinco bolas vermelhas numeradas de 1 a 5.
a) Qual é a probabilidade de que uma bola sorteada seja branca ou par?
b) Dado que uma bola é vermelha, qual é a chance ser ímpar?

4 - Simule o exercício resolvido na aula teórica e compare os resultados: Lançamos dois dados e observamos a variável aleatória X é igual a 1 se a soma for par ou igual a zero, caso contrário. Determine a distribuição de X.

Aula 3: Modelos Probabilísticos#

Valor esperado#

O valor esperado pode ser aproximado pela média de uma variável aleatória, fato jutificado pela Lei dos Grandes Números, que veremos nas próximas aulas.

Exemplo: Seja a variável aleatória X com distribuição abaixo. Calcule E[X] e V(X). $\( P(X=0) = 0.2,\quad P(X=1) = 0.2, \quad P(X = 2) = 0.6 \)\( O valor esperado: \)\( E[X] = 0*0.2 + 1*0.2 + 2*0.6 = 1.4 \)\( \)\( V(X) = E[X^2]-E[X]^2 = 0.64 \)$

import random
import numpy as np
n = 1000 #numero de experimentos
nA = 0
nB = 0
X = [0,0,1,1,2,2,2,2,2]
x_obs = []
for i in range(0,n):
    x_obs.append(random.choice(X))
    
print('Valor esperado de X:', np.mean(x_obs))
print('Variância de X:', np.std(x_obs)**2)
Valor esperado de X: 1.317
Variância de X: 0.6765109999999999

Lançamento de uma moeda#

Vamos simular o lançamento de uma moeda nsim vezes e comparar o valor \(p\) (probabilidade sair cara) com o valor obtido através de simulações.

import numpy as np
p = 0.6 # probabilidade de sair cara
nsim = 10 # num de experimentos
nhead = 0 # num de caras obtidas
saida = [] # armazena as saidas (cara:1, coroa:0)
for i in range(0, nsim):
    if(np.random.uniform() < p): # se menor que p, cara
        nhead = nhead + 1 # incrementa o contador de caras
        saida.append(1)
    else:
        saida.append(0)
print("Saida:", saida)
print("Frequencia teórica: p= ",p)
print("Frequencia de caras:", nhead/nsim)
Saida: [1, 1, 1, 1, 1, 0, 1, 1, 0, 0]
Frequencia teórica: p=  0.6
Frequencia de caras: 0.7

Distribuição binomial#

O lançamento de uma moeda \(n\) vezes é um processo de Bernoulli, pois há apenas duas saídas possíveis e os experimentos são independentes. Nesse caso, a probabilidade de sair \(k\) sucessos em \(n\) experimentos é calculada pela distribuição Binomial.

Vamos considerar o lançamento de uma moeda n vezes e calcular a distribuição teórica e a distribuição de probabilidade associada ao experimento.

from random import seed
from matplotlib import pyplot as plt
import numpy as np
from scipy.stats import binom
import math

seed(100) # semente do gerador de números aleatórios

n = 100 # numero de lançamentos
p = 0.3 # probabilidade de sair cara
Pk = np.zeros(n)
vk = np.arange(0,n)
ns = 1000 # numero de simulacoes
for j in range(0,ns): # faça para ns simulacoes
    S = 0 # numero de sucessos
    for i in range(0,n): # faça para n experimentos
        r = np.random.uniform() #
        if(r <= p): # se o sucesso
            S = S + 1
    Pk[S] = Pk[S] + 1
Pk=Pk/sum(Pk) # normaliza a distribuição de probabilidade
plt.figure(figsize=(10,6))
plt.xlim(0.8*np.min(vk[Pk>0]),1.2*np.max(vk[Pk>0]))
plt.bar(vk, Pk, label='Simulacao')

# curva teórica
Pkt = np.zeros(n+1) # valores teóricos da probabilidade
vkt = np.arange(0,n+1) # variação em k
for k in range(0,n+1): # varia de 0 até n
    C = (math.factorial(n)/(math.factorial(n-k)*math.factorial(k)))
    Pkt[k] = C*(p**k)*(1-p)**(n-k)
plt.plot(vkt, Pkt, 'r--', label='Prob. Teórica')
plt.xlabel('k', fontsize = 20)
plt.ylabel('P(k)',fontsize = 20)
plt.legend(fontsize = 15)
plt.show(True)
../_images/a0ddc6db670a20ad7d09c5ba39198ff2abccd88c66ee6941bfe026d610ff1621.png

Repetindo a análise usando funções do Python:

from scipy.stats import binom

seed(100) # semente do gerador de números aleatórios

n = 100 # numero de lançamentos
p = 0.3 # probabilidade de sair cara
ns = 1000 # numero de simulacoes
X = np.random.binomial(n, p, ns) # funcao para gerar valores de uma binomial

plt.figure(figsize=(10,6))
Pk, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7, 
                                rwidth=0.9)

# curva teórica
Pkt = np.zeros(n+1) # valores teóricos da probabilidade
vkt = np.arange(0,n+1) # variação em k
for k in range(0,n+1): # varia de 0 até n
    C = (math.factorial(n)/(math.factorial(n-k)*math.factorial(k)))
    Pkt[k] = C*(p**k)*(1-p)**(n-k)
plt.plot(vkt, Pkt, 'r--', label='Prob. Teórica')
plt.xlabel('k', fontsize = 20)
plt.ylabel('P(k)',fontsize = 20)
plt.legend(fontsize = 15)
plt.xlim(10,50)
plt.show(True)
../_images/07e998607758dbd1e97cb55f669b7da403647363ad4446d056fbdf175f8229ef.png

Exemplo: Em uma urna há 8 bolas brancas e 4 pretas. Retira-se 5 bolas com reposição. Calcule a probabilidade de que:
a) saiam duas bolas brancas.

Vamos construir uma função para calcular o valor exato.

import math
def binomial(n,p,k):
    C = (math.factorial(n)/(math.factorial(n-k)*math.factorial(k)))
    pk = C*(p**k)*(1-p)**(n-k)
    return pk

O valor teórico:

n = 5
p = 8/12
k = 2
print('Probabilidade:', binomial(n,p,k))
Probabilidade: 0.16460905349794244
from scipy.stats import binom
#binom.pmf(k) = choose(n, k) * p**k * (1-p)**(n-k)

ns = 1000 #numero de experimentos
X = ['B','B','B','B','B','B','B','B','P','P','P','P']
n = 5 # numero de bolas retiradas
k = 0
for i in range(0,ns):
    saida = []
    for j in range(0,n):
        bola = random.choice(X)
        saida.append(bola)
    nbrancas = 0
    for s in saida:
        if(s == 'B'):
            nbrancas = nbrancas + 1
    if(nbrancas == 2):
        k = k + 1 # se sair branca, temos mais um sucesso
    
print('Valor teórico:', binom.pmf(2, 5, 8/12))
print('Valor obtido = ', k/ns)
Valor teórico: 0.16460905349794241
Valor obtido =  0.163

b) saiam ao menos 3 pretas.

Valor teórico.

n = 5
p = 4/12
k = 2
pk = 0
for k in range(3,6):
    pk = pk + binomial(n,p,k)
print('Valor teórico:', pk)
Valor teórico: 0.20987654320987656
from scipy.stats import binom
#binom.pmf(k) = choose(n, k) * p**k * (1-p)**(n-k)

ns = 1000 #numero de experimentos
X = ['B','B','B','B','B','B','B','B','P','P','P','P']
n = 5 # numero de bolas retiradas
k = 0
for i in range(0,ns):
    saida = []
    for j in range(0,n):
        bola = random.choice(X)
        saida.append(bola)
    npretas = 0
    for s in saida:
        if(s == 'P'):
            npretas = npretas + 1
    if(npretas >= 3):
        k = k + 1
    
print('Valor teórico:', pk)
print('Valor obtido = ', k/ns)
Valor teórico: 0.20987654320987656
Valor obtido =  0.207

Modelo de Poisson#

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import factorial

lbd = 50 # taxa
ns = 1000 # numero de pontos extraídos de uma distribuição de Poisson
X = np.random.poisson(lam=lbd, size=ns)
plt.figure(figsize=(10,6))
P, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7, 
                                rwidth=0.9)
plt.xlabel('k', fontsize = 15)
plt.ylabel('P(k)',fontsize = 15)
plt.show(True)
../_images/24187bfd710d32579a9f10c58bcddc4e412e46f1546084397bc307f6751ef383.png

Exemplo: Em uma central telefônica, chegam 300 mensagens por hora. Qual é a probabilidade de que:

import numpy as np
import math
def Poisson(lbd, k):
    pk = np.exp(-lbd)*(lbd**k)/math.factorial(k)
    return pk

a) Em um minuto não ocorra nenhuma chamada.

lbd = 5 #numero de chamadas por minuto
k = 0
print("P(k = 0) = ",Poisson(lbd,k))
P(k = 0) =  0.006737946999085467

b) Em dois minutos ocorram duas chamadas.

lbd = 10 #numero de chamadas por 2 minutos
k = 2
print("P(k = 2) = ",Poisson(lbd,k))
P(k = 2) =  0.0022699964881242427

Modelo exponencial#

from scipy.stats import expon
import matplotlib.pyplot as plt
import numpy as np


alpha = 2
X = expon.rvs(scale=1/alpha,size=1000)
plt.figure(figsize=(8,5))
P, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7, 
                                rwidth=0.9)
plt.xlabel('k', fontsize = 15)
plt.ylabel('P(k)',fontsize = 15)
plt.show(True)
print('Esperanca teorica:', 1/alpha, 'Média amostral:', np.mean(X))
print('Variância teórica:', 1/alpha**2,'Variância amostral:', np.var(X))
../_images/b4e4fded721486502c57ff76d8dfaa2d35ea65a8025fb18a9157501ab968d1cf.png
Esperanca teorica: 0.5 Média amostral: 0.49408903956981065
Variância teórica: 0.25 Variância amostral: 0.2472047214966368

Exercícios de fixação#

1 - Seja a variável aleatória X com distribuição abaixo. Calcule E[X]. $\( P(X=0) = 0.4,\quad P(X=1) = 0.4, \quad P(X = 2) = 0.2 \)$ Calcule o valor esperado e simule o problema como feito anteriormente.

2 - Em uma urna há 5 bolas brancas e 9 pretas. Retira-se 5 bolas com reposição. Calcule a probabilidade de que:
a) saiam 3 bolas brancas.
b) saiam ao menos 5 pretas.

3 - Gere dados com distribuição de Poisson com \(\lambda = 5\). Verifique se \(E[X]=V[X]=\lambda\) usando simulação. Faça isso para diferences valores de \(\lambda\).

4 - Gere dados com distribuição exponencial com \(\alpha = 2\). Verifique se \(E[X] = 1/\alpha\) e \(=V[X]=1/\alpha^2\) usando simulação.

Distribuição Normal e Teoremas Limites#

Nessa aula, vamos estudar a distribuição normal. Vamos inicialmente gerar dados a partir dessa distribuição.

import numpy as np
import matplotlib.pyplot as plt
import math as math
 
# funcao que mostra a distribuicao teorica
def normal_dist(x , mean , sigma):
    prob_density = (1/(sigma*(math.sqrt(2*np.pi))))*np.exp(-0.5*((x-mean)/sigma)**2)
    return prob_density
 
#Calculate mean and Standard deviation.
mean = 75
sigma = 20
n = 10000
X = np.random.normal(mean, sigma, n)
plt.figure(figsize=(10,6))
Pk, bins, ignored = plt.hist(X, bins='auto', density=True, color='#0504aa',alpha=0.7, 
                                rwidth=0.9)

# define os valores de x
x = np.linspace(np.min(X),np.max(X),200)
# Distribuicao teorica
pdf = normal_dist(x,mean,sigma)

#Plotting the Results
plt.plot(x,pdf , color = 'red')
plt.xlabel('Data points')
plt.ylabel('Probability Density')
Text(0, 0.5, 'Probability Density')
../_images/05e8bff9896316a6fe5b25a993086ed9ec85dd61eb4b28cc367a300d9b17206a.png

Podemos resolver alguns exemplos para verificar algumas aplicações dessa distribuição.

Exemplo: Se \(X \sim \mathcal{N}(\mu=165,\,\sigma^{2}=9)\), calcule \(P(X<162)\).

import scipy.stats as st

media = 165
dp = 3
z = (162-media)/dp
print('Z:', z)
print(st.norm.cdf(z))
Z: -1.0
0.15865525393145707

Exemplo: Se \(X \sim \mathcal{N}(\mu=10,\,\sigma^{2}=4)\), calcule \(P(X>13)\).

import scipy.stats as st

media = 10
dp = 2
z = (13-media)/dp
print('Z:', z)
print(1-st.norm.cdf(z))
Z: 1.5
0.06680720126885809

Exemplo: O peso médio de 500 estudantes do sexo masculino de uma determinada universidade é 75,5 Kg e o desvio padrão é 7,5 Kg. Admitindo que os pesos são normalmente distribuídos, determine a percentagem de estudantes que pesam:

a) entre 60 e 77,5 Kg. $\( P(60 \leq X \leq 77,5) = P\left(\frac{60-\mu}{\sigma} \leq \frac{X-\mu}{\sigma} \leq \frac{77,5-\mu}{\sigma}\right)=P\left(\frac{60-\mu}{\sigma} \leq Z \leq \frac{77,5-\mu}{\sigma}\right) = \)\( \)\( = P\left(Z \leq \frac{77,5-\mu}{\sigma}\right)-P\left( Z \leq \frac{60-\mu}{\sigma}\right) \)$

import scipy.stats as st
media = 75.5
dp = 7.5
z1 = (60-media)/dp
z2 = (77.5-media)/dp
print('Probabilidade teórica:',st.norm.cdf(z2)-st.norm.cdf(z1))
Probabilidade teórica: 0.5857543024471563

Simulando:

media = 75.5
dp = 7.5
n = 100
X = np.random.normal(media, dp, n)
m = 0
for x in X:
    if x > 60 and x < 77.5:
        m = m + 1
print('Probabilidade (simulação):', m/n)
Probabilidade (simulação): 0.59

b) mais do que 92,5 Kg. $\( P(X \geq 92,5) = P\left( \frac{X-\mu}{\sigma}\geq \frac{92,5-\mu}{\sigma}\right) = P\left( Z \geq \frac{92,5-\mu}{\sigma}\right) = 1 - P\left( Z < \frac{92,5-\mu}{\sigma}\right) \)$

z1 = (92.5-media)/dp
p = 1-st.norm.cdf(z1)
print('Probabilidade teórica:',p)
Probabilidade teórica: 0.011705298080558313
media = 75.5
dp = 7.5
n = 100
X = np.random.normal(media, dp, n)
m = 0
for x in X:
    if x > 92.5:
        m = m + 1
print('Probabilidade (simulação):', m/n)
Probabilidade (simulação): 0.02

Exemplo: Uma máquina de bebidas está regulada de modo a servir uma média de 150ml por copo. Se a quantidade servida por copo seguir uma distribuição normal com desvio padrão de 20 ml, determine a percentagem de copos que conterão mais de 175ml de bebida.

\[ P(X \geq 175) = P\left( \frac{X-\mu}{\sigma}\geq \frac{175-\mu}{\sigma}\right) = P\left( Z \geq \frac{175-\mu}{\sigma}\right) = 1 - P\left( Z < \frac{175-\mu}{\sigma}\right) \]
media = 150
dp = 20
z = (175-media)/dp
print((1-st.norm.cdf(z))*100,'%')
10.564977366685536 %

Teorema Central do Limite#

A distribuição Normal aparece no Teorema Central do Limite.

Teorema: Seja uma amostra aleatória \((X_1,X_2,\ldots,X_n)\) retiradas de uma população com média \(\mu\) e variância \(\sigma\). A distribuição amostral de \(\bar{X}\) aproxima-se, para n grande, de uma distribuição normal com média \(E[\bar{X}]=\mu\) e variância \(\sigma^2/n\).

import scipy.stats as stats

vS =  [1, 2 , 4 , 8, 50, 100, 1000]# sample size
S = 500 # number of samples
mu = 2

for n in vS: #sample size
    vmean = []
    for s in range(0,S): # select s samples of size n
        X = np.random.uniform(0,2*mu, n) # X is generated from a uniform probability distribution
        #X = np.random.exponential(mu, n) # X is generated from an exponential probability distribution
        vmean.append(np.mean(X))
    plt.figure(figsize=(6,4))
    plt.hist(x=vmean, bins='auto', color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
    plt.xlabel(r'$\bar{X}$', fontsize=20)
    plt.ylabel(r'$P(\bar{X})$', fontsize=20)

    # Plot the theoretical curve
    #xt = plt.xticks()[0]  
    xmin, xmax = min(vmean), max(vmean)  
    lnspc = np.linspace(xmin, xmax, len(vmean))
    m, s = stats.norm.fit(vmean) # get mean and standard deviation  
    pdf_g = stats.norm.pdf(lnspc, m, s) # now get theoretical values in our interval  
    plt.plot(lnspc, pdf_g, label="Norm") # plot it
plt.show(True)
../_images/f347ba2b73bbecc970a49a1aad2ae2549c707b97bde52d36e14b5399d5b8cf2c.png ../_images/3bb83f68cc6a6d87dd31d71d0605f4ffeb160ec9d0ce52ccf7c0f8432c1353b3.png ../_images/a22fe7b51c7a7d9e09d2b7a875fa55b7a7982d89ae4b768cb623b79ed1d13d4b.png ../_images/0940b599569246664cb3d2c76157a83fab06c013b2f17fcad2084934ded1f891.png ../_images/7b6721ca331453654ac3d6c6e1d1bd37f9fac56ffc9972b78f7050e135245a89.png ../_images/534c60898e82cb7b8f1b58e2af1e77722d8b273a06d71d66ec37d6046f79f297.png ../_images/3b44e47684c2408129a83b518c2480ebd801bff262b686e496700ff16563442b.png

Vemos que independentemente da distribuição de \(X\), a distribuição da média amostral sempre segue uma normal quando o número de amostras é grande.

Vamos considerar alguns exemplos.

Exemplo: Seja a variável aleatória com distribuição de probabilidade: P(X=3)=0,4; P(X=6)=0,3; P(X=8)=0,3. Uma amostra com 40 observações é sorteada. Qual é a probabilidade de que a média amostral ser maior do que 5?

def esperanca(X,P):
    E = 0
    for i in range(0, len(X)):
        E = E + X[i]*P[i]
    return E
    
def variancia(X,P):
    E = 0; E2 = 0
    for i in range(0, len(X)):
        E = E + X[i]*P[i]
        E2 = E2 + (X[i]**2)*P[i]
    V = E2-E**2
    return V
    
X = [3,6,8]
P = [0.4,0.3,0.3]
E = esperanca(X,P)
V = variancia(X,P)
print("Esperança:", E, "Variância:",V)
Esperança: 5.4 Variância: 4.439999999999991

Valor teórico:

import scipy.stats as st
import numpy as np

mu = E
sigma = np.sqrt(V)
n = 40
x = 5
Z = (x - mu)/(sigma/np.sqrt(n))
pt = 1-st.norm.cdf(Z)
print('Probabilidade:',pt)
Probabilidade: 0.885046886863795

Vamos sortear várias amostras de tamanho n=40 e verificar qual a probabilidade da média dessa amostra ser maior do que 5.

import matplotlib.pyplot as plt

n = 40
ns = 1000 #numero de simulacoes
vx = [] # armazena a media amostral
for s in range(0,ns):
    A = np.random.choice(X, n, p=P)
    vx.append(np.mean(A))
plt.figure(figsize=(8,6))
plt.hist(x=vx, bins='auto',color='#0504aa', alpha=0.7, rwidth=0.85, density = True)
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)    
print("Media das amostras:", np.mean(vx), "Media da população:", E)
../_images/3201a2a65a297004180bb23dfc3fbdb1e36249dad068008b4e821e157a11a09e.png
Media das amostras: 5.408875 Media da população: 5.4

A probabilidade de ser maior do que 5:

nmaior = 0
for i in range(0, len(vx)):
    if(vx[i] > 5):
        nmaior = nmaior + 1
nmaior = nmaior/len(vx)
print("Probabilidade de ser maior do que 5:", nmaior, "Valor teórico:", pt)
Probabilidade de ser maior do que 5: 0.901 Valor teórico: 0.885046886863795

Lei dos grandes números#

Outro teorema fundamental e a lei dos grandes números. Basicamente, a lei forte dos grandes números é enunciada da seguinte forma:

Sejam \(X_1,X_2,\ldots, X_n\) um conjunto de variáveis aleatórias independentes e identicamente distribuídas. Seja \(E[X_i]=\mu\). Então, com probabilidade um, podemos afirmar: $\( \lim_{n \rightarrow \infty} \frac{X_1+X_2+\ldots+X_n}{n} \rightarrow \mu \)$

Podemos verificar essa lei através de simulações.

Vamos considerar o lançamento de uma moeda. Seja \(p\) a chance de sair cara. Então \(E[X)i]=p\), sendo \(i\) o i-ésimo lançamento da moeda e \(X=1\) se for cara ou \(X=0\), se sair coroa. Conforme notamos a seguir, quando aumentamos o número de simulações, a média de \(n\) simulações converge para a probabilidade \(p\).

import numpy as np
import matplotlib.pyplot as plt
 
p = 0.7
vp = [] # lista que armazena a fração de ocorrências em função do número de simulações nsim
vsim = [] # armazena o número de simulações
Nmax = 1000 # numero maximo de simulacoes
for nsim in np.arange(1,Nmax,1):
    nhead = 0 # numero de caras
    for i in range(1,nsim):
        if(np.random.uniform() < p):
            nhead = nhead + 1        
    vp.append(nhead/nsim)
    vsim.append(nsim)

plt.figure(figsize=(8,6))
plt.plot(vsim, vp, linestyle='-', color="blue", linewidth=2,label = 'Valor simulado')
plt.axhline(y=p, color='r', linestyle='--', label = 'Valor teorico')
plt.ylabel("Fração de caras", fontsize=20)
plt.xlabel("Numero de experimentos", fontsize=20)
plt.xlim([0.0, Nmax])
plt.ylim([0.0, 1.0])
plt.legend()
plt.show(True) 
../_images/6bb4b8f457c5338f754f860c73f1158c523cd6ab0368e1e71b4363cf42d8d624.png

Exercícios de fixação#

1 - Se \(X \sim \mathcal{N}(\mu=100,\,\sigma^{2}=10)\), calcule \(P(X<95)\).

2 - O peso médio de n estudantes do sexo masculino de uma determinada universidade é 75,5 Kg e o desvio padrão é 7,5 Kg. Admitindo que os pesos são normalmente distribuídos, determine a percentagem de estudantes que pesam entre 60 e 80 Kg. Considere n=100, 200 e 500. Compare os resultados.

3 - Obtenha os valor simulador (como feito anteriormente), para o problema a seguir. Compare a simulação com o valor teórico.

Uma máquina de bebidas está regulada de modo a servir uma média de 150ml por copo. Se a quantidade servida por copo seguir uma distribuição normal com desvio padrão de 20 ml, determine a percentagem de copos que conterão mais de 175ml de bebida.

4 - Verifique a lei dos grandes números para diferentes valores de \(p\) no exemplo anterior.

Teorema Central do Limite#

Teorema: Seja uma amostra aleatória \((X_1,X_2,\ldots,X_n)\) retiradas de uma população com média \(\mu\) e variância \(\sigma\). A distribuição amostral de \(\bar{X}\) aproxima-se, para \(n\) grande, de uma distribuição normal com média \(E[\bar{X}]=\mu\) e variância \(\sigma^2/n\).

Lei dos grandes números#

Outro teorema fundamental e a lei dos grandes números. Basicamente, a lei forte dos grandes números é enunciada da seguinte forma:

Sejam \(X_1,X_2,\ldots, X_n\) um conjunto de variáveis aleatórias independentes e identicamente distribuídas. Seja \(E[X_i]=\mu\). Então, com probabilidade um, podemos afirmar: $\( \lim_{n \rightarrow \infty} \frac{X_1+X_2+\ldots+X_n}{n} \rightarrow \mu \)$


Exercícios#

import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st # para uso de st.norm.cdf(Z)

Exercício 1:#

O tempo para desenvolver um servidor web em uma empresa é descrito por uma variável aleatória X, medida em dias, com distribuição normal de média 𝜇 = 45 e variância \(𝜎^2 = 400\). Calcule a probabilidade de que um novo servidor web será finalizado entre 40 e 45 dias.

\(X\): dias gastos pela empresa para confeccionar o software

\(\sigma^2: 400 \Rightarrow \sigma = 20\)

\(X \sim \mathcal{N}(\mu = 45, \sigma = 20)\)

\(P(40 < X < 45) = P\left(\frac{40 - \mu}{\sigma} < \frac{X - \mu}{\sigma} < \frac{45 - \mu}{\sigma}\right) = P\left(\frac{40 - 45}{20} < Z < \frac{45 - 45}{20}\right) = P\left(-0.25 < Z < 0\right)\)

\(P\left(-0.25 < Z < 0\right) = P(Z < 0) - P(Z < -0.25) = 0.5 - 0.4013 = 0.0987 \approx 9.87\%\)

A probabilidade de que o servidor fique prono entre 40 e 45 dias é de aproximadamente \(9.87\%\).

0.5 - 0.4013
0.09870000000000001

Exercício 2:#

Uma população é descrita pela seguinte distribuição de probabilidades:

\[𝑃(𝑋 = 2) = 0.2;𝑃(𝑋 = 4) = 0.4;𝑃(𝑋 = 6) = 0.4.\]

Uma amostra com 50 observações é sorteada. Calcule a probabilidade de que a média dessa amostra seja maior do que 5.

\(X = \{2, 4, 6\}\)

\(P(X = 2) = 0.2; P(X = 4) = 0.4; P(X = 6) = 0.4\)

\(E(X) = 2\cdot 0.2 + 4\cdot 0.4 + 6\cdot 0.4 = 4.4\)

\(V(X) = E(X^2) - (E(X))^2 =\\= [2^2\cdot P(Z = 2^2) + 4^2\cdot P(Z = 4^2) + 6^2\cdot P(Z = 6^2)] - (4.4)^2 = [2^2\cdot 0.2 + 4^2\cdot 0.4 + 6^2\cdot 0.4] - (4.4)^2 = 21.6 - 19.36 = 2.24\)


\(Z = X^2 = \{4, 16, 36\}\)

\(\sigma = \sqrt{V(X)} = \sqrt{2.24} = 1.49\)

\(X \sim \mathcal{N}(4.4, \sigma / \sqrt{50}) = \mathcal{N}(4.4, 0.21)\)

\(P(\bar{X} > 5) = ?\)


\(P\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}} > \frac{5 - \mu}{\sigma/\sqrt{n}}\right) = P\left(Z > \frac{5 - 4.4}{0.21}\right) = P\left(Z > 2.847\right) =\\= 1 - P(Z < 2.847) = 1 - 0.9977 = 0.0022 \approx 0.22\%\)

1 - st.norm.cdf(2.847)
0.002206668118837518
1.49 / np.sqrt(50), (5-4.4)/ (1.49 / np.sqrt(50))
(0.21071782079359117, 2.8474098571270354)
2*0.2+4*0.4+6*0.4, 2**2 * 0.2 + 4**2 *  0.4 + 6**2 * 0.4, 4.4**2, 21.6 - 19.36, np.sqrt(2.24)
(4.4, 21.6, 19.360000000000003, 2.240000000000002, 1.4966629547095767)

Simulação

X = [2,4,6]
P = [0.2,0.4,0.4]


ns = 100000 #numero de simulacoes
n = 50 # Número de amostras geradas
vx = [] # armazena a media amostral
for s in range(0,ns):
    A = np.random.choice(X, n, p=P) # Roleta viciada
    vx.append(np.mean(A))
plt.hist(vx)
(array([3.3000e+01, 3.5200e+02, 3.4530e+03, 1.1059e+04, 2.3700e+04,
        3.5800e+04, 1.7835e+04, 6.9950e+03, 7.1200e+02, 6.1000e+01]),
 array([3.48 , 3.656, 3.832, 4.008, 4.184, 4.36 , 4.536, 4.712, 4.888,
        5.064, 5.24 ]),
 <BarContainer object of 10 artists>)
../_images/026d5a019c3436d35dbdc719050a5570e9c2bbf717eccce2a4364f4c52eb318b.png

Repare que, em 10000 simulações de sorteio de 50 amostras de X de acordo com as probabilidades P, foram muito raras as ocasiões nas quais a média das 50 amostras superou 5. Especificamente,

n_maiorque5 = len([i for i in vx if i >= 5])

print('Em exatas ', n_maiorque5, 'simulações a média de 50 amostras superou 5, correspondendo a ', 100 * n_maiorque5 / len(vx),'% das simulações.')
print('Em outras palavras, em ', 100 * (1-n_maiorque5 / len(vx)),'% dos casos simulados, a média das 50 amostras geradas não superou o valor 5.')
Em exatas  226 simulações a média de 50 amostras superou 5, correspondendo a  0.226 % das simulações.
Em outras palavras, em  99.774 % dos casos simulados, a média das 50 amostras geradas não superou o valor 5.

Exercício 3:#

Em uma empresa de venda de softwares, a duração de conversas telefônicas (em minutos) segue o modelo exponencial com parâmetro 𝜆 = 1/5. Observando-se uma amostra aleatória de 50 dessas chamadas, qual será a probabilidade de que tais amostras em média não ultrapassem 6 minutos?

\( X \sim \lambda = 1/5 \)

\(E(X) = \frac{1}{\lambda} = \frac{1}{1/5} = 5\)

\(V(X) = \frac{1}{\lambda^2} = \frac{1}{\frac{1}{25}} = 25\)

\( \bar{X} \sim \mathcal{N}(5, 25)\)

\(P\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}} < \frac{6 - \mu}{\sigma/\sqrt{n}}\right) = P(Z < 1.414) = 0.9207 \approx 92.07\%\)

(6 - 5) / (5 / np.sqrt(50))
1.4142135623730951

Exercício 4:#

Qual deve ser o tamanho de uma amostra a ser retirada de uma população \(𝑋\sim \mathcal{N}(𝜇 = 100, 𝜎 = 50)\) para que \(𝑃(90 < 𝑋̅ < 110) = 0.95\)?

rela%C3%A7%C3%A3o_de_z.svg

Temos que \(P(Z < -z) = \frac{(1-A)}{2}\), sendo \(A\) a probabilidade exigida.

\(P\left(\frac{90-100}{50/\sqrt{n}} < Z < \frac{110-100}{50/\sqrt{n}}\right) = P\left(\frac{-10}{50/\sqrt{n}} < Z < \frac{10}{50/\sqrt{n}}\right) = P(-z < Z < z) = A\)

Uma vez que sabemos o valor de \(A\), basta agora calcularmos o valor de \(P(Z < -z)\) e, na sequência, conferirmos o valor de \(z\) na Tabela Padronizada.

\(P(Z < -z) = \frac{(1-A)}{2} = \frac{(1-0.95)}{2} = 0.025\)

Sabemos que \(-z = -1.96 \Rightarrow z = 1.96\)

Substituindo,

\(z = \frac{10}{50/\sqrt{n}} = \frac{\sqrt{n}}{5}\)

Então,

\(\frac{\sqrt{n}}{5} = 1.96 \Rightarrow\)

\(\frac{n}{25} = 1.96^2 \Rightarrow n = 25\cdot 1.96^2 = 96\)

O tamanho da amostra deve ser de, no mínimo, 96 elementos.

25*1.96*1.96
96.03999999999999

Propriedades: Considere \(X\) e \(Y\) variáveis aleatórias independentes e identicamente distribuídas (i.i.d.). Considere também \(\alpha, \beta, \gamma \in \mathbb{R}\). Então:

  1. \(E(\alpha X + \beta Y + \gamma) = \alpha E(X) + \beta E (Y) + \gamma\);

  2. \(V(\alpha X + \beta Y + \gamma) = \alpha^2 V(X) + \beta^2 V(Y)\).

Exercício 5:#

Seja \([𝑋_1, 𝑋_2, … , 𝑋_𝑛]\) uma amostra aleatória de uma população com média \(\mu\) e variância \(\sigma^2/n\). Mostre que para a estatística $\(𝑍 = \frac{(𝑋̅ − 𝜇)}{\left(\frac{𝜎}{\sqrt{𝑛}}\right)},\)\( temos \)𝐸[𝑍] = 0\( e \)𝑉(𝑍) = 1$.

\(\#\)Demonstração:

Sabemos que:

\(E\left(\bar{X}\right) = \mu\)

\(V\left(\bar{X}\right) = \sigma^2/n\)

Então, vamos avaliar a esperança e o desvio padrão de \(Z\):

\(E\left(Z\right) = E\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}}\right) = \frac{1}{\sigma/\sqrt{n}}E\left(\bar{X} - \mu\right) = \frac{1}{\sigma/\sqrt{n}}\left(E\left(\bar{X}\right) - E\left(\mu\right)\right) = \frac{1}{\sigma/\sqrt{n}} (\mu - \mu) = 0.\)

\(V\left(Z\right) = V\left(\frac{\bar{X} - \mu}{\sigma/\sqrt{n}}\right) = \frac{1}{\left(\sigma/\sqrt{n}\right)^2}V\left(\bar{X}\right) = \frac{\sigma^2/n}{\sigma^2/n} = 1.\)

\(\#\)Q.E.D.

Exercício 6:#

Mostre que se \((𝑋_1,𝑋_2, … , 𝑋_𝑛)\) (todas independentes) é uma variável aleatória de uma população com média 𝜇 e variância \(\sigma^2\), então \(𝐸[\bar{X}] = 𝜇\) e \(𝑉(\bar{X}) = \sigma^2/n\).

\(\#\)Demonstração:

Sabemos que:

  1. \(\bar{X} := \sum_{i=1}^n \frac{X_i}{n}\)

  2. \(E(X_i) = \mu\)

  3. \(V(X_i) = \sigma^2\)

Então,

\(E(\bar{X}) = E\left(\sum_{i=1}^n \frac{X_i}{n}\right) = E\left(\frac{1}{n}\sum_{i=1}^n X_i\right) = \frac{1}{n} \sum_{i=1}^n E(X_i) = \frac{1}{n} \sum_{i=1}^n \mu = \frac{1}{n} \cdot n\mu = \mu.\)

\(V(\bar{X}) = V\left(\sum_{i=1}^n \frac{X_i}{n}\right) = V\left(\frac{1}{n}\sum_{i=1}^n X_i\right) = \frac{1}{n^2} \sum_{i=1}^n V(X_i) =\\= \frac{1}{n^2} \sum_{i=1}^n \sigma^2 = \frac{1}{n^2} \cdot n\sigma^2 = \frac{\sigma^2}{n}.\)

Observação: \(\sum_{i=1}^n \alpha = \alpha + \alpha + \cdots +\alpha = n\cdot \alpha, \forall \alpha \in \mathbb{R}\).

Exercício extra (exercício de fixação 2 do material):#

O peso médio de \(n\) estudantes do sexo masculino de uma determinada universidade é 75,5 Kg e o desvio padrão é 7,5 Kg. Admitindo que os pesos são normalmente distribuídos, determine a quantidade (percentagem) de estudantes que pesam entre 60 e 80 Kg. Considere \(n=100, 200\) e \(500\). Compare os resultados.

\(X\): Peso de estudantes do sexo masculino

\(X \sim \mathcal{N}(75.5, 7.5)\)

\(P(60 < X < 80) = ?\)

\( P\left(\frac{60 - 75.5}{7.5} < Z < \frac{80 - 75.5}{7.5}\right) = P\left(-2.06 < Z < 0.6\right) = \\=P\left(Z < 0.6\right) - P\left(Z < -2.06\right) \approx 0.7257 - 0.0196 = 0.7060 = 70.60\%\)

Temos 70 alunos de 100 que pesam entre 60 e 80 kg.

Temos 141 alunos de 200 que pesam entre 60 e 80 kg.

Temos 353 alunos de 500 que pesam entre 60 e 80 kg.

(60-75.5)/7.5, (80-75.5)/7.5
(-2.066666666666667, 0.6)
st.norm.cdf(0.6), st.norm.cdf(-2.06), st.norm.cdf(0.6) - st.norm.cdf(-2.06)
(0.7257468822499265, 0.019699270409376895, 0.7060476118405495)
[(st.norm.cdf(0.6) - st.norm.cdf(-2.06))*n for n in [100, 200, 500]]
[70.60476118405495, 141.2095223681099, 353.02380592027475]

Aula 5 - Teste de hipóteses#

Francisco Aparecido Rodrigues, francisco@icmc.usp.br.
Universidade de São Paulo, São Carlos, Brasil.
https://sites.icmc.usp.br/francisco
Copyright: Creative Commons


Vamos resolver alguns exemplos usando simulação de Monte Carlo. Esses exemplos foram discutidos na aula.

Exemplo 1: Usando simulações#

Uma fábrica anuncia que o índice de nicotina dos cigarros de uma dada marca é igual a 20 mg por cigarro. Um laboratório realiza 20 análises do índice obtendo: 22, 19, 21, 22, 20, 18, 27, 20, 21, 19, 20, 22, 17, 20, 21,18, 25, 16, 20, 21. Sabe-se que o índice de nicotina dos cigarros dessa marca se distribui normalmente com variância 4 mg\(^2\). Pode-se aceitar a afirmação do fabricante, ao nível de 5%?

\(H_0: \mu = 20\)
\(H_1: \mu > 20\)

import numpy as np
import matplotlib.pyplot as plt

mu = 20 # hipotese a ser testada
sigma = 2 # desvio padrao populacional
n = 20 #tamanho da amostra
Ns = 1000 # numero de simulacoes
Xm=[] #distribuicao da media amostral
for s in range(1,Ns):
    x = np.random.normal(mu, sigma, n) # sorteia uma amostra de tamanho n
    Xm.append(np.mean(x))
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, 
             label = str(Ns), density=True)
plt.axvline(x=mu, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)  
../_images/bea9c501a238ede27e542eea8bf71160b0b8a1df8c61e9832fcc088c8b1015d6.png

Depois de gerar as amostras, vamos verificar a fração de observações que permitem que aceitemos \(H_0\).

X = [22, 19, 21, 22, 20, 18, 27, 20, 21, 19, 20, 22, 17, 20, 21,18, 25, 16, 20, 21]
xobs = np.mean(X)

alpha = 95
xc = np.percentile(Xm, alpha)
print('Xc=',xc,'  Xobs = ', xobs)
if(xobs < xc):
    print("Aceitamos H0")
else:
    print("Rejeitamos H0")
Xc= 20.682130613144107   Xobs =  20.45
Aceitamos H0

Podemos ainda ver esse resultado na figura.

plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
plt.axvline(x=xc, color='red', linestyle='--', label = 'xc1')
plt.axvline(x=xobs, color='green', linestyle='--', label = 'xobs')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.legend()
plt.show(True) 
../_images/7ea66a39ec2ac77ae624feb275c3d197de4ea3680e3de569bcd2e69a9ebc31be.png

Exemplo 2: Usando simulações#

Um pesquisador deseja estudar o efeito de certa substância no tempo de reação de seres vivos a um certo tipo de estímulo. Um experimento é desenvolvido com cobaias, que são inoculadas com a substância e submetidas a um estímulo elétrico, com seus tempos de reação (em segundos) anotados. Os seguintes valores foram obtidos:
T = [9,1;9,3;7,2;13,3;10,9;7,2;9,9;8,0;8,6;7,5]
Admite-se que, em geral, o tempo de reação tem distribuição Normal com média 8 segundos e desvio padrão 2 segundos. Entretanto, o pesquisador desconfia que o tempo médio sofre alteração por influência da substância. Verifique a nível 6% se o tempo de reação das cobaias submetidas à substância foi alterado.

\(H_0: \mu = 8\)
\(H_1: \mu \neq 8\)

import numpy as np
import matplotlib.pyplot as plt

mu = 8
sigma = 2
n = 10
Ns = 10000
Xm=[] #distribuicao da media amostral
for s in range(1,Ns):
    x = np.random.normal(mu, sigma, n) # sorteia uma amostra de tamanho n
    Xm.append(np.mean(x))
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, 
             label = str(Ns), density=True)
plt.axvline(x=mu, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)  
../_images/38e309769a0637368987e0e87eed034bb4c247b7f4a358962348efb2d242b669.png
X = [9.1,9.3,7.2,13.3,10.9,7.2,9.9,8.0,8.6,7.5]
xobs = np.mean(X)

alpha = 3 # ao nível 6%
xc1 = np.percentile(Xm, alpha)
xc2 = np.percentile(Xm, 100-alpha)
print('Xc1=',xc1, '  Xc2=', xc2, '  Xobs = ', xobs)
if(xobs < xc1 or xobs > xc2):
    print("Rejeitamos H0")
else:
    print("Aceitamos H0")
Xc1= 6.839844741968931   Xc2= 9.190933108643828   Xobs =  9.1
Aceitamos H0
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
plt.axvline(x=xc1, color='red', linestyle='--', label = 'xc1')
plt.axvline(x=xc2, color='orange', linestyle='--', label = 'xc2')
plt.axvline(x=xobs, color='green', linestyle='--', label = 'xobs')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.legend()
plt.show(True) 
../_images/cbf2ca93ab9cfbbe684b7593a67fb7ddf9dd909f4a213b63aa7e39e4788344ff.png

Exemplo 3: Solução exata#

Em uma cidade, acredita-se que 60% das pessoas aprovam o atual prefeito. Uma pesquisa foi realizada com 200 indivíduos, onde verificou-se que 104 disseram que aprovam o atual governo. Faça um teste ao nível 5% para determinar se a aprovação do prefeito é realmente igual a 60%.

\[\begin{split} \begin{aligned} &H_0: p = 0,6\\ &H_1: p < 0,6. \end{aligned} \end{split}\]
import scipy.stats
alpha = 0.05
z = scipy.stats.norm.ppf(alpha)
print('P(Z < %1.2f) = %1.2f' % (z, alpha))
P(Z < -1.64) = 0.05

O valor crítico:

\[ p_c = z_{\alpha}\sqrt{\frac{p(1-p)}{n}} + p \]
import numpy as np
p = 0.6
mu = p
s = p*(1-p)
n = 200
pc = z*(np.sqrt(p*(1-p)/200)) + p
print('pc = ', pc)
pc =  0.5430205989421221
pobs = 104/200
print('pobs = ',pobs)
pobs =  0.52

Como \(\hat{p}_{\mathrm{obs}} < \hat{p}_c \), rejeitamos \(H_0\) ao nível de 5%. Ou seja, a fração de pessoas que aprovam o atual prefeito é menor do que 60%.

Exemplo 4: Solução exata#

O número de horas de sono para uma pessoal saudável é próximo de 8 horas. por noite Pesquisadores suspeitam que um novo medicamento para depressão pode afetar esse número quando pacientes fazem seu uso. Para verificar essa influência do medicamento no número de horas de sono, são coletados dados de 10 pacientes, obtendo-se os valores 8,7,7,8,7,8,9,7,7,8, em horas. Verifique a hipótese dos pesquisadores ao nível 5%.

\(H_0: \mu = 8\)
\(H_1: \mu \neq 8\)

import scipy.stats
alpha = 0.05
n = 10
talpha = scipy.stats.t.ppf(alpha/2, n-1)
print('talpha = ',talpha)
talpha =  -2.262157162740992
X = [8,7,7,8,7,8,9,7,7,8]
s = np.std(X, ddof=1)
xobs = np.mean(X)
print('s = ', s)
print('xobs = ',xobs)
s =  0.6992058987801011
xobs =  7.6
n = len(X)
m = 8
xc1 = m + talpha*s/np.sqrt(n) 
xc2 = m - talpha*s/np.sqrt(n) 
print('xc1 =',xc1)
print('xc2 =',xc2)
xc1 = 7.49981823162488
xc2 = 8.500181768375121

Como xobs está dentro do intervalo \([x_{c1},x_{c2}]\), aceitamos \(H_0\) ao nível 5%.

Valor p#

Vamos considerar um exemplo. Sejam as hipóteses:
\(H_0: \mu = 10\)
\(H_1: \mu < 10\)
Assumimos que a população tem distribuição uniforme com desvio padrão \(\sigma\), definido abaixo.

import numpy as np
import matplotlib.pyplot as plt

mu = 10
sigma = 2
n = 50
Ns = 10000
Xm=[] #distribuicao da media amostral
for s in range(1,Ns):
    x = np.random.normal(mu, sigma, n) # sorteia uma amostra de tamanho n
    Xm.append(np.mean(x))
plt.figure(figsize=(8,4))
a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, 
             label = str(Ns), density=True)
plt.axvline(x=mu, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\bar{X}$', fontsize=20)
plt.ylabel(r'$P(\bar{X})$', fontsize=20)
plt.show(True)  
../_images/e9d40719bfdacc175ba3b72a25043fd4207c1760f0409e4777613cef291dcd37.png

Vamos supor que o valor observado \(\bar{x}_{obs}\) é definido abaixo. De acordo com o nível de significância, podemos aceitar ou rejeitar \(H_0\), conforme vemos abaixo.

xobs = 9.8
alphas = [5,10,20,25,30,40]
for alpha in alphas:
    xc = np.percentile(Xm, alpha)
    plt.figure(figsize=(6,3))
    a = plt.hist(x=Xm, bins=20, color='#0504aa', alpha=0.7, rwidth=0.85, density=True)
    plt.axvline(x=xc, color='red', linestyle='--', label = 'xc1')
    plt.axvline(x=xobs, color='green', linestyle='--', label = 'xobs')
    plt.xlabel(r'$\bar{X}$', fontsize=20)
    plt.ylabel(r'$P(\bar{X})$', fontsize=20)
    plt.legend()
    if(xobs < xc):
        plt.title("Rejeitamos H0"+r' $\alpha$ = ' + str(alpha/100))
    else:
        plt.title("Aceitamos H0"+r' $\alpha$ = ' + str(alpha/100))
    plt.show(True) 
../_images/86e3c88a23d0e648465e7e94a8dbbdbdcfcb4c9f165b77448dc66ee25250630b.png ../_images/efdf6b9f2c53454c9272b46996489a740faed96eae89a21b8d3d500a8c44f37e.png ../_images/14ae29585db87dc20a57762a6367fac36d41ddf677f24919dc39686847d979e9.png ../_images/535a13ae000525769669b5e63d35b378c228151e6b9c6dddcc68311f2597aba6.png ../_images/b1b9f9e05f31241b2b4e9589003b88fb2f973df642ab913104879b54089598df.png ../_images/1e6b3d47e4e654f7e03b7e8b8d86b92c339606cbcaf8dc203b291ea14b4dec34.png

O valor de \(\alpha\) em que há a transição entre aceitar ou rejeitar \(H_0\) é o valor p.

import numpy as np

xobs = 9.8
xcs = []
alphas = []
for alpha in np.arange(1,100,2):
    xc = np.percentile(Xm, alpha)
    xcs.append(xc)
    alphas.append(alpha)
    
plt.figure(figsize=(8,4))
plt.plot(alphas,xcs)
plt.axhline(y=xobs, color='r', linestyle='--', label = 'Media')
plt.xlabel(r'$\alpha$', fontsize=20)
plt.ylabel(r'$\bar{X}_c$', fontsize=20)

plt.grid(True)
plt.show(True)
../_images/ff222bd84b6af8f299affc8db05009946493e9378f7bb0874ca176ea826bfd0d.png

Podemos calcular o valor p: \(P(\bar{X} > \bar{x}_{obs}|\mu=\mu_0) = \alpha\):

pvalue = 0
for i in range(0, len(Xm)):
    if(Xm[i] < xobs):
        pvalue = pvalue + 1
pvalue = pvalue/len(Xm)
print('P-valor: ', pvalue)
P-valor:  0.24122412241224123

Mostrando no gráfico anterior.

xobs = 9.8
xcs = []
alphas = []
for alpha in np.arange(1,100,2):
    xc = np.percentile(Xm, alpha)
    xcs.append(xc)
    alphas.append(alpha)
    
plt.figure(figsize=(8,4))
plt.plot(alphas,xcs)
plt.axhline(y=xobs, color='red', linestyle='--', label = 'xobs')
plt.axvline(x=pvalue*100, color='blue', linestyle='--', label = 'p-valor')
plt.xlabel(r'$\alpha$', fontsize=20)
plt.ylabel(r'$\bar{X}$', fontsize=20)
plt.legend()
plt.grid(True)
plt.show(True)
../_images/f3e91ec090882e6a390a6fe721d4ccc26a99dfe69920cb89ff8c669752636ae0.png

Exemplo: Estudantes acreditam que a média da turma em um curso de estatística é igual a 65. O professor acredita que a média é maior. Para verificar essas hipóteses, ele seleciona notas de 10 estudantes, obtemos os valores [65, 65, 70, 67, 66, 63, 63, 68, 72, 71]. Assuma que as notas são normalmente distribuídas, calcule o valor p.

import numpy as np
X = [65, 65, 70, 67, 66, 63, 63, 68, 72, 71]
m = 65
n = len(X)
s = np.std(X, ddof=1)
xobs = np.mean(X)
print('s = ', s)
print('xobs = ',xobs)
s =  3.197221015541813
xobs =  67.0
\[ t_{\alpha} = \frac{x_{obs}-\mu}{s/\sqrt{n}} \]
talpha = (xobs - m)/(s/np.sqrt(n))
print('talpha = ', talpha)
talpha =  1.978141420187361
import scipy.stats

alpha = scipy.stats.t.cdf(talpha, n-1)
print('alpha =',1 - alpha)
alpha = 0.03964824393588806

Logo, o valor p é igual 0,039, indicando uma forte evidência para rejeitarmos \(H_0\).

Comparação de duas médias#

Uma das principais aplicações do teste de hipóteses é na seleção de atributos. Vamos comparar duas distribuições e verificar se elas possuem a mesma média. Vamos formular as hipóteses: $\( H_0: \mu_1 = \mu_2 \)\( \)\( H_a: \mu_1 \neq \mu_2 \)$

import numpy as np
import matplotlib.pyplot as plt

n = 1000
mu1 = 0
sigma1 = 2
x1 = np.random.normal(mu1, sigma1, n) # sorteia uma amostra de tamanho n

mu2 = 0
sigma2 = 2
x2 = np.random.normal(mu2, sigma2, n) # sorteia uma amostra de tamanho n

plt.figure(figsize=(8,4))
a1 = plt.hist(x=x1, bins=20, color='blue', alpha=0.7, rwidth=0.85, density=True)
a2 = plt.hist(x=x2, bins=20, color='red', alpha=0.7, rwidth=0.85, density=True)
plt.show(True)  
../_images/3967b3c77ad0b976617973d35974f4a436cc9502fa1feca7b8be1f16d53e495c.png

Fazendo um teste de hipóteses para comparar as médias das duas distribuições:

from scipy import stats

t_stat, p = stats.ttest_ind(x1,x2)
print(f't={t_stat}, p={p}')
t=1.2607979946933925, p=0.20752884296966367

Ou seja, como o valor p é alto, podemos concluir que as distribuições possuem a mesma média (aceitamos \(H_0\))

Vamos agora considerar duas distribuições com médias distintas.

import numpy as np
import matplotlib.pyplot as plt

n = 1000
mu1 = 0
sigma1 = 2
x1 = np.random.normal(mu1, sigma1, n) # sorteia uma amostra de tamanho n

mu2 = 0.5
sigma2 = 2
x2 = np.random.normal(mu2, sigma2, n) # sorteia uma amostra de tamanho n

plt.figure(figsize=(8,4))
a1 = plt.hist(x=x1, bins=20, color='blue', alpha=0.7, rwidth=0.85, density=True)
a2 = plt.hist(x=x2, bins=20, color='red', alpha=0.7, rwidth=0.85, density=True)
plt.show(True)  
../_images/35e468a06bc49b13b376a7c4b590b0bb780fab37fead2c27ea1c7b61d17f8f3e.png
from scipy import stats

t_stat, p = stats.ttest_ind(x1,x2)
print(f't={t_stat}, p={p}')
t=-4.871868681827615, p=1.19246724185128e-06

Nesse segundo caso, vemos que o valor p é próximo de zero, o que nos permite rejeitar \(H_0\)

Portanto, podemos usar o teste de hipóteses para realizar uma seleção de atributos, onde atributos que não conseguem discriminar duas classes, devem ser removidos do conjunto de dados.

Seleção de atributos#

Vamos incialmente gerar um conjunto de dados, onde as duas primeiras colunas possuem a mesma média para duas classes, mas as outras duas permite separar as classes.

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs

# duas primeiras variaveis
centers = [(0, 0), (0, 0)]
data = make_blobs(n_samples=100, centers=centers, cluster_std=3, 
                  shuffle=False, random_state=42)
X1 = data[0] # atributos das observacoes
y = data[1] # classe conhecida inicialmente
# mostra os dados
plt.scatter(X1[:,0], X1[:,1], c=y, cmap='viridis', s=50, alpha=0.9)
plt.xlabel('x', fontsize=20)
plt.ylabel('y', fontsize=20)
plt.title('Conjunto de dados 1')
plt.show(True)

# terceira e quarta variaveis
centers = [(-3, -3), (1,1)]
data = make_blobs(n_samples=100, centers=centers, cluster_std=3, 
                  shuffle=False, random_state=42)
X2 = data[0] # atributos das observacoes
y = data[1] # classe conhecida inicialmente
# mostra os dados
plt.scatter(X2[:,0], X2[:,1], c=y, cmap='viridis', s=50, alpha=0.9)
plt.xlabel('x', fontsize=20)
plt.ylabel('y', fontsize=20)
plt.title('Conjunto de dados 2')
plt.show(True)
../_images/5149780db5413fc5311a3694ab795f8e2dea8251f0e6df57eb6891bb487f2ca9.png ../_images/b622f84ada14647d314429767068f23bcb87dc237c5bc6d4874d755946cce722.png

Note quem as duas últimas variáveis permitem separar os dados tanto nos eixos x quando y.

Vamos construir um conjunto único com os dados gerados.

X = np.column_stack((X1,X2))

Vamos fazer a classificação usando todo o conjunto de dados.

from sklearn.model_selection import train_test_split
p = 0.2 # fraction of elements in the test set
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size = p, random_state = 42)

from sklearn.naive_bayes import GaussianNB
from sklearn import metrics

model = GaussianNB()
model.fit(x_train, y_train)

y_pred = model.predict(x_test)
print('Accuracy: ', model.score(x_test, y_test))
Accuracy:  0.9

Para realizar a seleção dos atributos, usammos Anova (https://pt.wikipedia.org/wiki/Análise_de_variância), que usa o teste de hipóteses.

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif


fvalue_selector = SelectKBest(f_classif, k=2) 
#seleciona os dois atributos mais importantes usando teste de hipoteses

X_kbest = fvalue_selector.fit_transform(X, y)
plt.scatter(X_kbest[:,0], X_kbest[:,1], c=y, cmap='viridis', s=50, alpha=0.9)
plt.show(True)
../_images/7a69c5a2504a0dd631df9bcae20f1ece2387c8df410dda6990cea335788de449.png

Notamos que quando selecionamos os dois principais atributos, o métod escolhe os atributos 3 e 4, conforme o esperado (veja o gráfico anterior).

Realizando a classificação usando apenas os atributos selecionados:

from sklearn.model_selection import train_test_split
p = 0.2 # fraction of elements in the test set
x_train, x_test, y_train, y_test = train_test_split(X_kbest, y, test_size = p, 
                                                    random_state = 42)

from sklearn.naive_bayes import GaussianNB
from sklearn import metrics

model = GaussianNB()
model.fit(x_train, y_train)

y_pred = model.predict(x_test)
print('Accuracy: ', model.score(x_test, y_test))
Accuracy:  0.9

Logo, vemos que mesmo usando dois atributos, mantemos a taxa de acertos. Portanto, a seleção de atributos permitiu um modelo mais simples (com menos atributos), mas com o mesmo desempenho.

Aula 6 - Inferência Bayesiana#

Programa#

  • O paradigma Bayesiano.

  • Os diferentes tipos de prioris.

  • Distribuições conjugadas.

  • Estimação Bayesiana.

  • Densidade preditiva.

  • Computação Bayesiana.

  • Exemplos com PyMC3.

Referências e leituras recomendadas:

  1. Migon, H. S., Gamerman, D. and Louzada, F. (2014). Statistical Inference: An Integrated Approach, Second Edition, CRC Press.

  2. Caffo, B. (2016). Statistical Inference for Data Science. Leanpub. Disponível em https://leanpub.com/LittleInferenceBook

  3. https://pt.wikipedia.org/wiki/Inferência_bayesiana

  4. datawookie/talks

O paradigma Bayesiano#

Seja uma amostra aleatória \(X_1,\ldots, X_n\) que vem de um modelo \(p(x|\theta)\), \(\theta \in \Theta\) e sejam \(x_1,\ldots,x_n\) os dados observados.

Sob o paradigma clássico ou frequentista, \(\theta\) é um parâmetro fixo e desconhecido.

Sob o paradigma Bayesiano, consideramos modelos probabilísticos para representar a incerteza a respeito de \(\theta\).

Se temos informação a priori sobre o parâmetro \(\theta\), antes de observar os dados, por que não usá-la?

Exemplo:#

Na aplicação de dados bancários que vimos na Aula 1, suponha que antes de observar os dados, exista um conhecimento prévio de que a proporção p de inadimplentes esteja em torno de 20%. Como incorporar esse conhecimento prévio? Uma possibilidade seria considerar uma distribuição a priori beta para a proporção p.

# Mais sobre a distribuição beta: https://pt.wikipedia.org/wiki/Distribui%C3%A7%C3%A3o_beta

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from scipy.stats import beta

# Conjuntos de parâmetros da distribuição a priori para a proporção de inadimplentes
a_b_params = ((0.1, 0.1), (1, 1), (2, 8), (10, 5))
p = np.linspace(0, 1, 100)

# Plota as densidades da beta para cada conjunto de parâmetros
plt.figure(figsize=(13,3))
for i, (a, b) in enumerate(a_b_params):
    plt.subplot(1, len(a_b_params), i+1)

    prior = beta(a, b)

    plt.plot(p, prior.pdf(p))    
    plt.xlabel(r'$p$')
    plt.title("a = {:.1f}, b = {:.1f}".format(a, b))
    plt.tight_layout()
../_images/9bbd83c7e14d390394e535fec27b5dec075942fe217dc059dd2c148c0212877e.png

Teorema de Bayes#

Ver, por exemplo: https://pt.wikipedia.org/wiki/Teorema_de_Bayes

Sejam

  • \(p(\theta)\) a distribuição a priori para \(\theta\).

  • \(l(\theta,x) = p(x|\theta)\) a verossimilhança de \(\theta\).

  • \(p(\theta|x)\) a distribuição a posteriori de \(\theta\).

O Teorema de Bayes ilustra o aumento de informação com a introdução do conhecimento a priori

\[p(\theta|x) = \displaystyle \frac{p(x,\theta)}{p(x)} = \frac{p(x|\theta)p(\theta)}{p(x)} = \frac{p(x|\theta) p(\theta)}{\displaystyle\int p(\theta,x) d\theta}.\]

Em outras palavras:

distribuição a posteriori \(\propto\) verossimilhança \(\times\) distribuição a priori

Para um valor fixo de \(x\), as duas fontes de informação para \(\theta\) são

  • a função \(l(\theta;x)=p(x|\theta)\) fornece a plausibilidade ou verossimilhança de cada um dos possíveis valores de \(\theta\) e

  • \(p(\theta)\) é chamada distribuição a priori de \(\theta\),

que, combinadas, levam à distribuição a posteriori de \(\theta\), \(p(\theta|x)\).

paradigmabayesiano.png

Assim, a forma usual do teorema de Bayes é \(p(\theta|x) \propto l(\theta;x)p(\theta).\)

O termo omitido \(p(x)\) é apenas uma constante normalizadora e não depende de \(\theta\).

Para \(x\) fixo, a verossimilhança fornece a plausibilidade de cada um dos possíveis valores de \(\theta\).

Já a distribuição a priori \(p(\theta)\) incorpora o conhecimento do pesquisador.

Essas duas quantidades combinadas são levadas à distribuição a posteriori de \(\theta\).

A distribuição a posteriori de \(\theta\) dados \(x_1,\ldots,x_n\) observados é dada por:

\[p(\theta|x_1,\ldots,x_n) = \displaystyle\frac{p(x_1,\ldots,x_n|\theta)p(\theta)}{\int_{\Theta} p(x_1,\ldots,x_n|\theta)p(\theta)d\theta}\]
  • \(p(x_1,\ldots,x_n|\theta) = \displaystyle\prod_{i=1}^n p(x_i|\theta) = L(\theta|x_1,\ldots,x_n)\) é a função de verossimilhança de \(\theta\).

  • O denominador $\(\int_{\Theta} p(x_1,\ldots,x_n|\theta)p(\theta)d\theta = C(x_1,\ldots,x_n).\)$

É comum escrever que

\(p(\theta|x_1,\ldots,x_n) = \displaystyle\frac{ L(\theta|x_1,\ldots,x_n) p(\theta)}{ C(x_1,\ldots,x_n)}\propto L(\theta|x_1,\ldots,x_n) p(\theta)\),

Distribuição preditiva#

A constante normalizadora da posteriori pode ser facilmente recuperada pois \(p(\theta|x)=kp(x|\theta)p(\theta)\) onde

\(k^{-1}= \int p(x|\theta)p(\theta)d\theta=E_\theta[p(X|\theta)]= p(x)\)

chamada distribuição preditiva (ou marginal) de \(X\).

Esta é a distribuição esperada para a observação \(x\) dado \(\theta\). Assim,

  • Antes de observar \(X\) podemos checar a adequação da priori fazendo predições via \(p(x)\).

  • Se \(X\) observado recebia pouca probabilidade preditiva então o modelo deve ser questionado, revisado, ou existe observação aberrante.

Se, após observar \(X=x\), estamos interessados na previsão de uma quantidade \(Y\), também relacionada com \(\theta\), e descrita probabilisticamente por \(p(y|\theta)\) então

\[p(y|x)=\int p(y,\theta| x)d\theta =\displaystyle \int p(y|\theta,x)p(\theta| x)d\theta\]

Os conceitos de priori e posteriori são relativos àquela observação que está sendo considerada no momento. Assim, \(p(\theta| x)\) é a posteriori de \(\theta\) em relação a \(X\) (que já foi observado) mas é a priori de \(\theta\) em relação a \(Y\) (que não foi observado ainda).

Após observar \(Y=y\) uma nova posteriori (relativa a \(X=x\) e \(Y=y\)) é obtida aplicando-se novamente o teorema de Bayes.

Exemplo (Gamerman e Migon, 1993)

Um médico “desconfia” que um paciente pode ter uma doença. Baseado na sua experiência, no seu conhecimento sobre esta doença e nas informações dadas pelo paciente, ele assume que a probabilidade do paciente ter a doença é 0.7. A quantidade de interesse, desconhecida, é definida como

\(\theta = \left\{\begin{array}{l} 1,\quad \mbox{se o paciente tem a doença,} \\ 0,\quad \mbox{se o paciente não tem a doença.}\end{array}\right.\)

Para aumentar sua quantidade de informação sobre a doença o médico aplica um teste \(X\) relacionado com \(\theta\) através da distribuição

\(P(X=1|\theta=0)=0.40\) e

\(P(X=1|\theta=1)=0.95\)

e o resultado do teste foi positivo (ou seja, observou-se \(X=1\)).

É bem intuitivo que a probabilidade de doença deve ter aumentado após este resultado e a questão aqui é quantificar este aumento. Usando o teorema de Bayes, segue que

\(P(\theta=1|X=1)\propto l(\theta=1;X=1)p(\theta=1)=(0.95)(0.7)=0.665\)

\(P(\theta=0|X=1)\propto l(\theta=0;X=1)p(\theta=0)=(0.40)(0.3)=0.120.\)

A constante normalizadora é tal que \(P(\theta=0|X=1)+P(\theta=1|X=1)=1\), i.e.,

\(k(0.665)+k(0.120)=1\) e \(k=1/0.785\).

Portanto, a distribuição a posteriori de \(\theta\) é

\(P(\theta=1|X=1)=0.665/0.785=0.847\)

\(P(\theta=0|X=1)=0.120/0.785=0.153.\)

O aumento na probabilidade de doença não foi muito grande porque a verossimilhança \(l(\theta=0;X=1)\) também era grande (o modelo atribuia uma plausibilidade grande para \(\theta=0\) mesmo quando \(X=1\)).

Agora o médico aplica outro teste \(Y\) cujo resultado está relacionado a \(\theta\) através da seguinte distribuição

\(P(Y=1|\theta=0)=0.04\) e

\(P(Y=1|\theta=1)=0.99.\)

Mas antes de observar o resultado deste teste é interessante obter sua distribuição preditiva.

Como \(\theta\) é uma quantidade discreta segue que

\(p(y|x)=\sum_\theta p(y|\theta)p(\theta|x)\)

e note que \(p(\theta|x)\) é a priori em relação a \(Y\).

Assim,

\(P(Y=1|X=1)=P(Y=1|\theta=0)P(\theta=0|X=1)+P(Y=1|\theta=1)P(\theta=1|X=1)\)

\(=(0.04)(0.153) + (0.99)(0.847) = 0.845\)

\(P(Y=0|X=1)=1-P(Y=1|X=1) = 0.155.\)

O resultado deste teste foi negativo (\(Y=0\)).

Neste caso, é também intuitivo que a probabilidade de doença deve ter diminuido e esta redução será quantificada por uma nova aplicação do teorema de Bayes,

\(P(\theta=1|X=1,Y=0)\propto\displaystyle l(\theta=1;Y=0)P(\theta=1|X=1)\propto\displaystyle (0.01)(0.847)=0.0085\)

\(P(\theta=0|X=1,Y=0)\propto\displaystyle l(\theta=0;Y=0)P(\theta=0|X=1)\propto\displaystyle (0.96)(0.153)=0.1469.\)

A constante normalizadora é \(1/(0.0085+0.1469)=1/0.1554\) e assim a distribuição a posteriori de \(\theta\) é

\(P(\theta=1|X=1,Y=0)=0.0085/0.1554=0.055\)

\(P(\theta=0|X=1,Y=0)=0.1469/0.1554=0.945.\)

Verifique como a probabilidade de doença se alterou ao longo do experimento

\(P(\theta=1) = \left\{\begin{array}{ll} 0.7 & \mbox{antes dos testes}\\0.847 & \mbox{após o teste X}\\ 0.055 & \mbox{após X e Y}\end{array}\right.\)

Note também que o valor observado de \(Y\) recebia pouca probabilidade preditiva. Isto pode levar o médico a repensar o modelo, i.e.,

(i) Será que \(P(\theta =1)=0.7\) é uma priori adequada?

(ii) Será que as distribuições amostrais de \(X\) e \(Y\) estão corretas? O teste \(X\) é tão inexpressivo e \(Y\) é realmente tão poderoso?

Os diferentes tipos de prioris#

Destacamos as distribuições a priori

  • Priori não-informativa

    • Uniforme

    • Priori vaga (às vezes imprópria)

  • Priori informativa

    • Conhecimento do pesquisador dá informação sobre os parâmetros

  • Priori conjugada

    • Priori e posteriori tem a mesma distribuição, a menos dos parâmetros (em geral facilita os cálculos)

Distribuições conjugadas#

Leituras recomendadas:

Vantagem de usar distribuições a priori conjugadas: principalmente ganho de custo computacional.

Priori Núcleo da Verossimilhança Posteriori
$\theta$ proporção Beta Bernoulli Beta
$\theta$ média Normal Normal Normal
taxa de falha Gama Poisson Gama
Dirichlet Multinomial Dirichlet

Exemplo de priori conjugada beta-Bernoulli#

Ver https://towardsdatascience.com/conjugate-prior-explained-75957dc80bfb

No exemplo do banco, se considerarmos que

  • \(X=\left\{ \begin{array}{lll} 1, &\mbox{se o cliente é classificado como inadimplente,} \\ 0, &\mbox{caso contrário.} \end{array}\right.\)

  • \(X \sim Bernoulli(p)\)

  • Verossimilhança:

Para \(n\) suficientemente grande, pelo TLC sabemos que a distribuição amostral de \(\bar{X}\) se aproxima da normal (será usada para graficar a verossimilhança) $\(\bar{X} \sim N\left(p, \displaystyle{\frac{p(1-p)}{n}}\right).\)$

Além disso, \(Y = \sum_{i=1}^{n} X_i \sim binomial(n, p)\).

  • Priori: \(p \sim beta(2, 8)\)

  • Posteriori: \(p|k \sim beta(k+a, n-k+b)\)

onde \(k\) é o número de sucessos observados na amostra.

import pandas as pd

# Indique o seu diretório se necessário
#pkgdir = '/hdd/MBA/ECD/Data'
#dados = pd.read_csv(f'{pkgdir}/dados_banco.csv', index_col=0)

# Dados banco - Leitura dos dados
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)

dados.head()
Sexo Idade Empresa Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
Cliente
75928 M 32 Privada 5719.00 933.79 0.0 0.0 6023.68 0
52921 F 28 Privada 5064.00 628.37 0.0 0.0 1578.24 0
8387 F 24 Autônomo 4739.00 889.18 0.0 0.0 2578.70 0
54522 M 30 Pública 5215.00 1141.47 0.0 0.0 4348.96 0
45397 M 30 Autônomo 5215.56 520.70 0.0 0.0 1516.78 1
# Vamos trabalhar com uma amostra

import random

a = 2
b = 8

amostra = dados.sample(n=500, replace=False, random_state=10)

n = len(amostra)
k = amostra['Inadimplente'].sum()
posteriori = beta(a + k, n - k + b) 

k/n
0.268
from scipy.stats import norm

# Eixo x entre 0 e 1 de .002 em .002.
x_axis = np.arange(0, 1, 0.002)

# Plota as densidades da beta para cada conjunto de parâmetros
plt.figure(figsize=(20,6))
    
prior = beta(a, b)

p_chapeu = amostra['Inadimplente'].mean()
dp = np.sqrt(p_chapeu*(1-p_chapeu)/n)

media = p_chapeu
dp = np.sqrt(media*(1-media)/n)

plt.s = 0
plt.rcParams.update({'font.size': 22})

plt.plot(x_axis, norm.pdf(x_axis, media, dp), label='verossimilhança')
plt.plot(x_axis, prior.pdf(x_axis), label='priori')
plt.plot(x_axis, posteriori.pdf(x_axis), label='posteriori')    
plt.xlabel(r'$p$')
plt.legend()
<matplotlib.legend.Legend at 0x7fb310141900>
../_images/5702038c82fa20d9f09e7d3e389e3976003e0ad33b6ebadd75793763b6833302.png
# Estimador bayesiano EAP (Esperança a posteriori)
print('Média: %.2f' % posteriori.mean())


# E para calcular um intervalo de credibilidade, decidimos uma probabilidade 
# Por exemplo 95% para a credibilidade
# Uma maneira seria definir que 2,5% de cada cauda como os limites do intervalo (chamado intervalo simétrico)
# Este método é válido quando a posteriori se aproxima de uma distribuição simétrica, pois nesse caso tende a gerar o intervalo com menor amplitude
# A seguir, apresentamos outra solução com um intervalo de credibilidade de menor amplitude.

LI = posteriori.ppf(.025)
LS = posteriori.ppf(.975)
print("Intervalo com 95% de credibilidade: ({:.3f}, {:.3f})".format(LI,LS))
Média: 0.27
Intervalo com 95% de credibilidade: (0.229, 0.306)

Estimação Bayesiana#

Leituras recomendadas:

Introdução à Teoria da Decisão#

Um problema de decisão fica completamente especificado pela descrição dos seguintes espaços:

(i) Espaço do parâmetro ou estados da natureza, \(\Theta\).

(ii) Espaço dos resultados possíveis de um experimento, \(\Omega\).

(iii) Espaço de possíveis ações, \(A\).

Uma regra de decisão \(\delta\) é uma função definida em \(\Omega\) que assume valores em \(A\), i.e. \(\delta:\Omega\rightarrow A\). A cada decisão \(\delta\) e a cada possível valor do parâmetro \(\theta\) podemos associar uma perda \(L(\delta,\theta)\) assumindo valores positivos. Definimos assim uma função de perda.

Definição: O risco de uma regra de decisão, denotado por \(R(\delta)\), é a perda esperada a posteriori, i.e. $\(R(\delta)=E_{\theta\vert\mathbf{x}} [L(\delta,\theta)].\)$

Uma regra de decisão \(\delta^*\) é ótima se tem risco mínimo, i.e. \(R(\delta^*)<R(\delta), ~\forall \delta\). Esta regra será denominada regra de Bayes e seu risco, risco de Bayes.

Exemplo:

Um laboratório farmaceutico deve decidir pelo lançamento ou não de uma nova droga no mercado. O laboratório só lançará a droga se achar que ela é eficiente mas isto é exatamente o que é desconhecido.

Podemos associar um parâmetro \(\theta\) aos estados da natureza:

  • droga é eficiente (\(\theta=1\)),

  • droga não é eficiente (\(\theta=0\))

e as possíveis ações como

  • lança a droga (\(\delta=1\)),

  • não lança a droga (\(\delta=0\)).

Suponha que foi possível construir a seguinte tabela de perdas levando em conta a eficiência da droga,

eficiente ($\theta=1$) não eficiente ($\theta=0$)
lança ($\delta=1$) -500 600
não lança ($\delta=0$) 1500 100

Vale notar que estas perdas traduzem uma avaliação subjetiva em relação à gravidade dos erros cometidos.

Suponha agora que a incerteza sobre os estados da natureza é descrita por

\(P(\theta=1)=\pi\), \(0<\pi<1\)

avaliada na distribuição atualizada de \(\theta\) (seja a priori ou a posteriori).

Note que, para \(\delta\) fixo, \(L(\delta,\theta)\) é uma variável aleatória discreta assumindo apenas dois valores com probabilidades \(\pi\) e \(1-\pi\).

Assim, usando a definição de risco obtemos que

\(\displaystyle R(\delta=0) = \displaystyle E(L(0,\theta))=\pi 1500 +(1-\pi) 100 = 1400\pi + 100\)

\(\displaystyle R(\delta=1) = \displaystyle E(L(1,\theta))=\pi(-500)+(1-\pi) 600 =-1100\pi + 600\)

Uma questão que se coloca aqui é, para que valores de \(\pi\) a regra de Bayes será de lançar a droga.

Não é difícil verificar que as duas ações levarão ao mesmo risco, i.e.

\(R(\delta=0)=R(\delta=1)\) se somente se \(\pi=0.20\).

Além disso, para \(\pi<0.20\) temos que \(R(\delta=0)<R(\delta=1)\) e a regra de Bayes consiste em não lançar a droga enquanto que \(\pi>0.20\) implica em \(R(\delta=0)>R(\delta=1)\) e a regra de Bayes deve ser de lançar a droga.

Estimadores de Bayes#

Considere uma amostra aleatória \(X_1,\dots,X_n\), tomada de uma distribuição com função de (densidade) de probabilidade \(p(x\vert\theta)\), onde o valor do parâmetro \(\theta\) é desconhecido.

Em um problema de inferência como este o valor de \(\theta\) deve ser estimado a partir dos valores observados na amostra.

Se \(\theta\in\Theta\) então é razoável que os possíveis valores de um estimador \(\widehat{\theta}(\mathbf{x})\) também devam pertencer ao espaço \(\Theta\).

Além disso, um bom estimador é aquele para o qual, com alta probabilidade, apresenta o seguinte erro $\(\widehat{\theta}(\mathbf{x})-\theta\)$ próximo de zero.

Funções de perda#

Para cada possível valor de \(\theta\) e cada possível estimativa \(a\in\Theta\) vamos associar uma perda \(L(a,\theta)\) de modo que quanto maior a distância entre \(a\) e \(\theta\) maior o valor da perda. A perda esperada a posteriori é dada por

\[E[L(a,\theta)\vert\mathbf{x}] = \int L(a,\theta)p(\theta\vert\mathbf{x}) d\theta\]

O estimador de Bayes será aquele que minimiza a perda esperada.

Função de perda quadrática#

\[L(a,\theta)=(a-\theta)^2\]

O estimador de Bayes para \(\theta\) será a média de sua distribuição atualizada (EAP: expected a posteriori).

Função de perda absoluta#

(introduz punições que crescem linearmente com o erro de estimação)

\[L(a,\theta)=\vert a-\theta\vert\]

O estimador de Bayes para \(\theta\) é a mediana de sua distribuição atualizada.

Função de perda 0-1#

(associam uma perda fixa a um erro cometido, não importando sua magnitude)

\[\begin{split}L(a,\theta)=\left\{ \begin{array}{ccc} 1 &\mbox{se}& |a-\theta|>\epsilon \\ 0 &\mbox{se}& |a-\theta|<\epsilon \end{array}\right.\end{split}\]

para todo \(\epsilon>0\).

Neste caso, o estimador de Bayes é a moda da distribuição atualizada de \(\theta\) (MAP: maximum a posteriori).

A moda da posteriori de \(\theta\) também é chamado de estimador de máxima verossimilhança generalizado (EMVG) e é o mais fácil de ser obtido dentre os estimadores vistos até agora.

Exemplo:

Suponha que queremos estimar a proporção \(\theta\) de itens defeituosos em um grande lote. Para isto será tomada uma amostra aleatória \(X_1,\dots,X_n\) de uma distribuição de Bernoulli com parâmetro \(\theta\). Usando uma priori conjugada beta(\(a,b\)), após observar a amostra a distribuição a posteriori é beta(\(a+k,b+n-k\)) onde \(k=\sum_{i=1}^n x_i\).

A média desta distribuição beta é dada por \((a+k)/(a+b+n)\) e portanto o estimador de Bayes de \(\theta\) usando perda quadrática é

\(\delta(\mathbf{x})= \displaystyle\frac{a+\sum_{i=1}^n X_i}{a+b+n}.\)

Estimação por Intervalos#

Pode-se desenvolver a estimação por intervalos por meio do intervalo de credibilidade (ou intervalo de confiança Bayesiano) baseado no distribuição a posteriori.

Definição:

C é um intervalo de credibilidade de 100(1-\(\alpha\))\(\%\), ou nível de credibilidade (ou confiança) \(1-\alpha\), para \(\theta\) se \(P(\theta\in C)\ge 1-\alpha\).

Obs: Quanto menor for o tamanho do intervalo, mais concentrada é a distribuição do parâmetro, ou seja o tamanho do intervalo informa sobre a dispersão de \(\theta\).

O intervalo com o menor comprimento possível é obtido tomando-se os valores de \(\theta\) com maior densidade a posteriori, e esta idéia é expressa matematicamente na definição abaixo.

Definição:

Um intervalo de credibilidade \(C\) de 100(1-\(\alpha\))% para \(\theta\) é de máxima densidade a posteriori (MDP) se \(C=\{\theta\in\Theta:p(\theta\vert\mathbf{x})\ge k(\alpha)\}\) onde \(k(\alpha)\) é a maior constante tal que \(P(\theta\in C)\ge 1-\alpha\).

Usando esta definição, todos os pontos dentro do intervalo MDP terão densidade maior do que qualquer ponto fora do intervalo.

Um problema com os intervalos MDP é que eles não são invariantes a transformações 1 a 1, a não ser para transformações lineares. O mesmo problema ocorre com intervalos de comprimento mínimo na inferência clássica.

Computação Bayesiana#

Leituras recomendadas:

A obtenção de informações a partir da distribuição a posteriori dos parâmetros pode envolver a avaliação de probabilidades ou esperanças, que exigem métodos computacionais baseados em simulações, como

  • Método de Monte Carlo simples

  • Monte Carlo com função de importância,

  • Algoritmo de Metropolis-Hastings,

  • Amostrador de Gibbs,

  • Método do Bootstrap Bayesiano,

  • Monte Carlo via cadeias de Markov (MCMC),

  • Monte Carlo Hamiltoniano (HMC),

  • No-U-Turn Sampler (NUTS).

HMC e NUTS aproveitam as informações de gradiente da probabilidade e alcançam uma convergência muito mais rápida do que os métodos de amostragem tradicionais, especialmente para modelos mais complexos.

O pacote PyMC3 do Python usam a programação probabilística para executar os métodos de HMC como o NUTS. Esse tipo de programação permite a especificação flexível e ajuste de modelos estatísticos bayesianos com sintaxe intuitiva e legível, embora poderosa, que é próxima da sintaxe natural que os estatísticos usam para descrever modelos.

Exemplos#

!pip install theano
!pip install arviz==0.15.1
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting theano
  Downloading Theano-1.0.5.tar.gz (2.8 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.8/2.8 MB 44.4 MB/s eta 0:00:00
?25h  Preparing metadata (setup.py) ... ?25l?25hdone
Requirement already satisfied: numpy>=1.9.1 in /usr/local/lib/python3.10/dist-packages (from theano) (1.22.4)
Requirement already satisfied: scipy>=0.14 in /usr/local/lib/python3.10/dist-packages (from theano) (1.10.1)
Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from theano) (1.16.0)
Building wheels for collected packages: theano
  Building wheel for theano (setup.py) ... ?25l?25hdone
  Created wheel for theano: filename=Theano-1.0.5-py3-none-any.whl size=2668109 sha256=da55416411fe89a525787e2707fc012f1bc6d368d17de994c8779baa5f2948bb
  Stored in directory: /root/.cache/pip/wheels/d9/e6/7d/2267d21a99e4ab8276f976f293b4ff23f50c9d809f4a216ebb
Successfully built theano
Installing collected packages: theano
Successfully installed theano-1.0.5
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: arviz==0.15.1 in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.22.4)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.10.1)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (23.1)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (2022.12.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.1.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (0.5.1)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz==0.15.1) (3.8.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.0.7)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (4.39.3)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.4.4)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (8.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (3.0.9)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz==0.15.1) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz==0.15.1) (1.16.0)
import warnings

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm
#!pip show arviz

Exemplo beta-Bernoulli: clientes do banco#

with pm.Model() as model:
    p = pm.Beta("p", 2, 8) #priori
    obs = pm.distributions.discrete.Bernoulli("obs", p, observed=amostra['Inadimplente'])

    idata = pm.sample(2000, tune=1500, return_inferencedata=True)
100.00% [3500/3500 00:02<00:00 Sampling chain 0, 0 divergences]
100.00% [3500/3500 00:02<00:00 Sampling chain 1, 0 divergences]
az.summary(idata)
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
p 0.267 0.019 0.231 0.303 0.0 0.0 2111.0 2931.0 1.0
az.plot_posterior(idata);
../_images/859c86f5cf3eb23b12c949cd7c7d0d7344d61123065303322b7601301ed3a7b7.png
az.plot_forest(idata, r_hat=True);
../_images/199d200f971900a5d58a829113a2488a00d6f1bf8432de9b2cc6801eed107ce3.png

Exemplo: normal com dados simulados#

Fonte: https://docs.pymc.io/pymc-examples/examples/pymc3_howto/api_quickstart.html

with pm.Model() as model:
    mu = pm.Normal("mu", mu=0, sigma=1) # priori
    obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100)) # verossimilhança
model.basic_RVs
[mu, obs]
model.free_RVs
[mu]
model.observed_RVs
[obs]

Variáveis aleatórias não observáveis#

with pm.Model():
    x = pm.Normal("x", mu=0, sigma=1)

Variáveis aleatórias observáveis#

with pm.Model():
    obs = pm.Normal("x", mu=0, sigma=1, observed=np.random.randn(100))

Inferência#

Amostragem#

with pm.Model() as model:
    mu = pm.Normal("mu", mu=0, sigma=1)
    obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100))

    idata = pm.sample(2000, tune=1500, return_inferencedata=True)
100.00% [3500/3500 00:02<00:00 Sampling chain 0, 0 divergences]
100.00% [3500/3500 00:02<00:00 Sampling chain 1, 0 divergences]
idata.posterior.dims
Frozen({'chain': 2, 'draw': 2000})

Amostragem com 6 cadeias em paralelo#

with pm.Model() as model:
    mu = pm.Normal("mu", mu=0, sigma=1)
    obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100))

    idata = pm.sample(cores=4, chains=6, return_inferencedata=True)
100.00% [12000/12000 00:10<00:00 Sampling 6 chains, 0 divergences]
idata.posterior["mu"].shape
(6, 1000)

Podemos incluir passos com outros métodos#

with pm.Model() as model:
    mu = pm.Normal("mu", mu=0, sigma=1)
    obs = pm.Normal("obs", mu=mu, sigma=1, observed=np.random.randn(100))

    step = pm.Metropolis()
    trace = pm.sample(1000, step=step)
100.00% [2000/2000 00:00<00:00 Sampling chain 0, 0 divergences]
100.00% [2000/2000 00:00<00:00 Sampling chain 1, 0 divergences]
with pm.Model() as model:
    mu = pm.Normal("mu", mu=0, sigma=1)
    sd = pm.HalfNormal("sd", sigma=1)
    obs = pm.Normal("obs", mu=mu, sigma=sd, observed=np.random.randn(100)) #verossimilhança

    step1 = pm.Metropolis(vars=[mu])
    step2 = pm.Slice(vars=[sd])
    idata = pm.sample(10000, step=[step1, step2], cores=4, return_inferencedata=True)
100.00% [44000/44000 00:43<00:00 Sampling 4 chains, 0 divergences]

Análise de resultados#

az.plot_trace(idata);
../_images/d7b45c21a26f549536df32279e325ee6d79167ad6580de8255fb4054a74a75a3.png

Estatística de Gelman-Rubin (R chapéu)#

az.summary(idata)
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
mu 0.012 0.108 -0.188 0.219 0.001 0.001 6116.0 5771.0 1.0
sd 1.113 0.079 0.969 1.262 0.000 0.000 39031.0 29563.0 1.0
az.plot_forest(idata, r_hat=True);

az.plot_posterior(idata);
../_images/b2f2742cb77647d2f447a2dad55ce81be8876ea57b0750f92e601e88be927b29.png ../_images/473a8328af069bdd01d7cecf07a585117f7398f4330bd1250767064123cab76b.png
az.plot_posterior(idata);
../_images/473a8328af069bdd01d7cecf07a585117f7398f4330bd1250767064123cab76b.png

Prática 1#

Inferência Bayesiana#

Exercício:#

Na aplicação de dados bancários que vimos na Aula 1 (clientes do banco), considere diferentes tamanhos de amostra e diferentes prioris e veja como interferem na posteriori.

Além disso, altere os parâmetros da distribuição a priori (chamados hiperparâmetros) e verifique os efeitos na distribuição a posteriori.

Considere, por exemplo, uma distribuição a priori beta para a proporção p, com diferentes hiperparâmetros. Compare os resultados com os obtidos com uma distribuição a priori uniforme para p.

Exemplo de priori conjugada beta-Bernoulli#

Ver https://towardsdatascience.com/conjugate-prior-explained-75957dc80bfb

No exemplo do banco, se considerarmos que

  • \(X=\left\{ \begin{array}{lll} 1, &\mbox{se o cliente é classificado como inadimplente,} \\ 0, &\mbox{caso contrário.} \end{array}\right.\)

  • \(X \sim Bernoulli(p)\)

  • Verossimilhança:

Para \(n\) suficientemente grande, pelo TLC sabemos que a distribuição amostral de \(\bar{X}\) se aproxima da normal $\(\bar{X} \sim N\left(p, \displaystyle{\frac{p(1-p)}{n}}\right)\)$

Além disso, \(Y = \sum_{i=1}^{n} X_i \sim binomial(np, np(1-p))\).

import pandas as pd

# Dados banco - Leitura dos dados
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)

dados.head()
Sexo Idade Empresa Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
Cliente
75928 M 32 Privada 5719.00 933.79 0.0 0.0 6023.68 0
52921 F 28 Privada 5064.00 628.37 0.0 0.0 1578.24 0
8387 F 24 Autônomo 4739.00 889.18 0.0 0.0 2578.70 0
54522 M 30 Pública 5215.00 1141.47 0.0 0.0 4348.96 0
45397 M 30 Autônomo 5215.56 520.70 0.0 0.0 1516.78 1

Considere diferentes tamanhos de amostra. Como interferem na posteriori?

# Vamos trabalhar com uma amostra
from scipy.stats import beta
import numpy as np
import matplotlib.pyplot as plt
import random

a = 2
b = 8

amostra = dados.sample(n=500, replace=False, random_state=10)

n = len(amostra)
k = amostra['Inadimplente'].sum()
posteriori = beta(a + k, n - k + b) 

k/n
0.268

Considere diferentes tipos de priori, por exemplo beta ou uniforme. Como interferem na posteriori?

Priori 1 - beta#

  • Priori: \(p \sim beta(2, 8)\)

  • Posteriori: \(p|k \sim beta(k+a, n-k+b)\)

onde \(k\) é o número de sucessos observados na amostra.

from scipy.stats import norm

# Eixo x entre 0 e 1 de .002 em .002.
x_axis = np.arange(0, 1, 0.002)

# Plota as densidades da beta para cada conjunto de parâmetros
plt.figure(figsize=(20,6))
    
prior = beta(a, b)

p_chapeu = amostra['Inadimplente'].mean()
dp = np.sqrt(p_chapeu*(1-p_chapeu)/n)

media = p_chapeu
dp = np.sqrt(media*(1-media)/n)

plt.s = 0
plt.rcParams.update({'font.size': 22})

plt.plot(x_axis, norm.pdf(x_axis, media, dp), label='verossimilhança')
plt.plot(x_axis, prior.pdf(x_axis), label='priori')
plt.plot(x_axis, posteriori.pdf(x_axis), label='posteriori')    
plt.xlabel(r'$p$')
plt.legend()
<matplotlib.legend.Legend at 0x7fb8a1f9c310>
../_images/5702038c82fa20d9f09e7d3e389e3976003e0ad33b6ebadd75793763b6833302.png
# Estimador bayesiano EAP (Esperança a posteriori)
print('Média: %.2f' % posteriori.mean())


# E para calcular um intervalo de credibilidade, decidimos uma probabilidade 
# Por exemplo 95% para a credibilidade
# Uma maneira seria definir que 2,5% de cada cauda como os limites do intervalo (chamado intervalo simétrico)
# Este método é válido quando a posteriori se aproxima de uma distribuição simétrica, pois nesse caso tende a gerar o intervalo com menor amplitude
# A seguir, apresentamos outra solução com um intervalo de credibilidade de menor amplitude.

LI = posteriori.ppf(.025)
LS = posteriori.ppf(.975)
print("Intervalo com 95% de credibilidade: {:.3f}, {:.3f})".format(LI,LS))
Média: 0.27
Intervalo com 95% de credibilidade: 0.229, 0.306)

Exemplos#

#!pip install pymc3==3.11.1
!pip install theano
!pip install arviz==0.15.1
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: theano in /usr/local/lib/python3.10/dist-packages (1.0.5)
Requirement already satisfied: numpy>=1.9.1 in /usr/local/lib/python3.10/dist-packages (from theano) (1.22.4)
Requirement already satisfied: scipy>=0.14 in /usr/local/lib/python3.10/dist-packages (from theano) (1.10.1)
Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from theano) (1.16.0)
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: arviz==0.15.1 in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.22.4)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.10.1)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (23.1)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (2022.12.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.1.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (0.5.1)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz==0.15.1) (3.8.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.0.7)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (4.39.3)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.4.4)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (8.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (3.0.9)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz==0.15.1) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz==0.15.1) (1.16.0)
import warnings

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pymc as pm

Modelo beta-Bernoulli: clientes do banco#

Com amostra de 500 observações, considere, por exemplo, priori beta (10,5). Compare com os resultados obtidos em aula.

with pm.Model() as model:
    p = pm.Beta("p", 10, 5)
    obs = pm.distributions.discrete.Bernoulli("obs", p, observed=amostra['Inadimplente'])

    idata = pm.sample(2000, tune=1500, return_inferencedata=True)
100.00% [3500/3500 00:02<00:00 Sampling chain 0, 0 divergences]
100.00% [3500/3500 00:07<00:00 Sampling chain 1, 0 divergences]
az.summary(idata)
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
p 0.279 0.02 0.241 0.314 0.0 0.0 1759.0 2476.0 1.0
az.plot_posterior(idata);
../_images/ad30a586c77e21ba5b010f7f47106850b81dae6e65cb9b1292e3c116957359fc.png
az.plot_forest(idata, r_hat=True);
../_images/e301175f4cab4ea114bb56383d6933e6787ea02f8f25318513c8ffbb4f99cb02.png

Análise de resultados#

az.plot_trace(idata);
../_images/c73de12038365241112396ca95c84a98c5c5fbf607497425faf56ce58ee9ba14.png
az.summary(idata)
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
p 0.279 0.02 0.241 0.314 0.0 0.0 1759.0 2476.0 1.0
az.plot_forest(idata, r_hat=True);

az.plot_posterior(idata);
../_images/e301175f4cab4ea114bb56383d6933e6787ea02f8f25318513c8ffbb4f99cb02.png ../_images/ad30a586c77e21ba5b010f7f47106850b81dae6e65cb9b1292e3c116957359fc.png

Priori uniforme#

Considere agora, por exemplo, priori uniforme para a proporção p. Como interfere na posteriori?

Refaça as análises com diferentes tamanhos de amostra, por exemplo 100 ou 10000.

with pm.Model() as model:
    p = pm.Uniform("p")
    obs = pm.distributions.discrete.Bernoulli("obs", p, observed=amostra['Inadimplente'])

    idata = pm.sample(2000, tune=1500, return_inferencedata=True)
100.00% [3500/3500 00:06<00:00 Sampling chain 0, 0 divergences]
100.00% [3500/3500 00:05<00:00 Sampling chain 1, 0 divergences]
az.summary(idata)
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
p 0.269 0.02 0.234 0.307 0.001 0.0 1434.0 2742.0 1.0
az.plot_posterior(idata);
../_images/febd93aed38dcc9d41eb0376c7f66571b0bd8e2a5c3251bfbc876ef8db976190.png
az.plot_forest(idata, r_hat=True);
../_images/eb3d8d95f24d17309087815369c801dafca29261cf5330aa438bcd7e00298c2f.png
### Análise de resultados

az.plot_trace(idata);
../_images/b32021b30621a24ace80b4b85ce35b3425b725c09d1571406977858cee6a89cf.png
az.summary(idata)
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
p 0.269 0.02 0.234 0.307 0.001 0.0 1434.0 2742.0 1.0
az.plot_forest(idata, r_hat=True);
../_images/eb3d8d95f24d17309087815369c801dafca29261cf5330aa438bcd7e00298c2f.png
az.plot_posterior(idata);
../_images/febd93aed38dcc9d41eb0376c7f66571b0bd8e2a5c3251bfbc876ef8db976190.png

Prática 2#

Inferência Bayesiana#

Aplicação#

Traduzida e adaptada de https://github.com/WillKoehrsen/probabilistic-programming/blob/master/Estimating Probabilities with Bayesian Inference.ipynb

Suponha que visitemos uma reserva de animais selvagens onde sabemos que os únicos animais são leões, tigres e ursos, mas não sabemos quantos de cada há nesse local. Durante o passeio, vemos 3 leões, 2 tigres e 1 urso. Supondo que todos os animais tenham chances iguais de aparecer em nossa amostra, estime a prevalência de cada espécie. Qual é a probabilidade de o próximo animal que vemos ser um urso?

Modelo

O modelo subjacente é multinomial com parâmetros \( p_k \) (veja, por exemplo, https://en.wikipedia.org/wiki/Multinomial_distribution).

A distribuição a priori de \( p_k \) é uma distribuição Dirichlet (veja, por exemplo, https://en.wikipedia.org/wiki/Dirichlet_distribution).

O vetor \( \alpha \) é um parâmetro da distribuição a priori de Dirichlet, também chamado de hiperparâmetro de concentração.

Uma Distribuição Multinomial com uma Priori de Dirichlet é referida como uma Distribuição Multinomial de Dirichlet.

O modelo pode ser expresso em equações como:

\[\begin{split}{\begin{array}{lclcl}{\boldsymbol {\alpha }}=(\alpha _{1},\ldots ,\alpha _{K}) = {\text{hiperparâmetros de concentração}}\\\mathbf {p} \mid {\boldsymbol {\alpha }} = (p_{1},\ldots ,p_{K}) \sim \operatorname {Dir} (K,{\boldsymbol {\alpha }})\\\mathbb {X} \mid \mathbf {p} = (x_{1},\ldots ,x_{K}) \sim \operatorname {Mult} (K,\mathbf {p} )\end{array}}\end{split}\]

Nosso objetivo é estimar \(p_\text{leões}\), \(p_\text{tigres}\), \(p_\text{ursos}\) dado o vetor observado \(c = [c_{leões}, c_{tigres}, c_{ursos}]\)

Distribuição Multinomial

Este problema é um exemplo clássico da distribuição multinomial que descreve uma situação em que temos n tentativas independentes, cada uma com k resultados possíveis. Neste problema, \(n = 6\) e \(k = 3\). Caracterizado pela probabilidade de cada resultado, \( p_k \) que deve somar 1. Nosso objetivo é encontrar \( p_ \text {leões} \), \( p_ \text{tigres} \), \( p_ \text{ursos} \) dadas as observações \(c_{leões}= 3\), \(c_{tigres}= 2\) e \(c_{ursos}= 1\).

Distribuição Dirichlet

Uma distribuição multinomial com uma priori Dirichlet é chamada de modelo Dirichlet-Multinomial. A distribuição de Dirichlet é caracterizada por \( \alpha \), o vetor dos hiperparâmetros de concentração.

Hiperparâmetros

O vetor \(\alpha \) é o hiperparâmetro, um parâmetro de uma distribuição a priori.

O vetor hiperparâmetro pode ser considerado como pseudo-contagens, que usamos para mostrar nossa crença a priori para a prevalência de cada espécie. Se quisermos um hiperparâmetro uniforme refletindo que acreditamos que a chance de observar qualquer espécie é a mesma, definimos cada elemento de alfa igual, como \( \alpha = [1, 1, 1] \). Podemos aumentar ou diminuir o efeito das a prioris aumentando ou diminuindo os valores. Isso pode ser útil quando temos mais ou menos confiança em nossas crenças a prioris.

Instale os pacotes abaixo se necessário.

!pip install theano
!pip install arviz==0.15.1
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: theano in /usr/local/lib/python3.10/dist-packages (1.0.5)
Requirement already satisfied: numpy>=1.9.1 in /usr/local/lib/python3.10/dist-packages (from theano) (1.22.4)
Requirement already satisfied: scipy>=0.14 in /usr/local/lib/python3.10/dist-packages (from theano) (1.10.1)
Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from theano) (1.16.0)
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: arviz==0.15.1 in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.22.4)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.10.1)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (23.1)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (2022.12.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (1.1.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz==0.15.1) (0.5.1)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz==0.15.1) (3.8.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.0.7)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (0.11.0)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (4.39.3)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (1.4.4)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (8.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (3.0.9)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz==0.15.1) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz==0.15.1) (2022.7.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz==0.15.1) (1.16.0)
import requests
import io
url="https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/codigos/utils.py"
leitura = requests.get(url)
utils = leitura.text
exec(utils)
import pandas as pd
import numpy as np
import arviz as az

# Visualizations
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('fivethirtyeight')
plt.rcParams['font.size'] = 22
%matplotlib inline

from matplotlib import MatplotlibDeprecationWarning

import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
warnings.filterwarnings('ignore', category=MatplotlibDeprecationWarning)

import pymc as pm

Especificidades do problema#

Usaremos principalmente uma versão dos hiperparâmetros, \( \alpha = [1, 1, 1] \), que são os parâmetros da Dirichlet. Teste outros valores para ver como isso muda a solução do problema. Lembre-se que alterar os hiperparâmetros é inserir conhecimentos diferentes a respeito das distribuições a priori.

# observations
animals = ['lions', 'tigers', 'bears']
c = np.array([3, 2, 1])

# hyperparameters (initially all equal)
alphas = np.array([1, 1, 1])

alpha_list = [np.array([0.1, 0.1, 0.1]), np.array([1, 1, 1]),
                    np.array([5, 5, 5]), np.array([15, 15, 15])]

Expected Value#

Fonte: http://users.cecs.anu.edu.au/~ssanner/MLSS2010/Johnson1.pdf

Uma maneira de obter uma estimativa pontual da prevalência é usar o valor esperado da posteriori para \( p_k \). O valor esperado de uma Distribuição Multinomial de Dirichlet é: $\({\displaystyle \operatorname {E} [p_{i}\mid \mathbb {X} ,{\boldsymbol {\alpha }}]={\frac {c_{i}+\alpha _{i}}{N+\sum _{k}\alpha _{k}}}}\)$

display_probs(dict(zip(animals, (alphas + c) / (c.sum() + alphas.sum()))))
Species: lions    Prevalence: 44.44%.
Species: tigers   Prevalence: 33.33%.
Species: bears    Prevalence: 22.22%.

Modelo Bayesiano#

Usaremos o PYMC3 e parâmetro \(\alpha = [1, 1, 1]\) para a priori. A verossimilhança é multinomial e a priori para os parâmetros é Dirichlet.

with pm.Model() as model:
    # Parameters of the Multinomial are from a Dirichlet
    parameters = pm.Dirichlet('parameters', a=alphas, shape=3)
    # Observed data is from a Multinomial distribution
    observed_data = pm.Multinomial(
        'observed_data', n=6, p=parameters, shape=3, observed=c)

Amostrando do modelo#

A célula abaixo mostra 1000 amostras da posteriori em 2 cadeias. Usamos 500 amostras para ajuste que são descartadas. Isso significa que para cada variável aleatória do modelo - os parâmetros - teremos 2.000 valores retirados da distribuição posterior.

with model:
    # Sample from the posterior
    trace = pm.sample(draws=1000, chains=2, tune=500, 
                      discard_tuned_samples=True)
100.00% [1500/1500 00:03<00:00 Sampling chain 0, 0 divergences]
100.00% [1500/1500 00:03<00:00 Sampling chain 1, 0 divergences]
summary = pm.summary(trace)
summary.index = animals
summary
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
lions 0.441 0.152 0.150 0.710 0.004 0.003 1612.0 1290.0 1.0
tigers 0.338 0.148 0.075 0.605 0.004 0.003 1351.0 899.0 1.0
bears 0.221 0.132 0.017 0.459 0.003 0.002 1966.0 1269.0 1.0

Inspecionando os resultados#

Análise dos resultados com PYMC:

summary = az.summary(trace)
summary.index = animals
summary
mean sd hdi_3% hdi_97% mcse_mean mcse_sd ess_bulk ess_tail r_hat
lions 0.441 0.152 0.150 0.710 0.004 0.003 1612.0 1290.0 1.0
tigers 0.338 0.148 0.075 0.605 0.004 0.003 1351.0 899.0 1.0
bears 0.221 0.132 0.017 0.459 0.003 0.002 1966.0 1269.0 1.0

Podemos perceber que a média das amostras está muito próxima do valor esperado, mas também devemos observar as estimativas intervalares.

Gráficos de diagnóstico#

#ax = pm.plot_posterior(trace, varnames = ['parameters'], 
                       #figsize = (20, 10), edgecolor = 'k');

ax = az.plot_posterior(trace, figsize = (20, 10));


plt.rcParams['font.size'] = 22
for i, a in enumerate(animals):
    ax[i].set_title(a);
../_images/84573a94e1c2479ce80c11696d2705df57fe5885d041b8f0327dead0381de36e.png

Traceplot#

The traceplot shows a kernel density estimate (a smoothed histogram) on the left and all the samples that were drawn on the right. We collapse the chains on th plots (combined = True) but in reality we drew 2 independent chains.

Máximo a Posteriori usando PyMC#

with model:
    # Find the maximum a posteriori estimate
    map_ = pm.find_MAP()
    
display_probs(dict(zip(animals, map_['parameters'])))
100.00% [9/9 00:00<00:00 logp = -1.8042, ||grad|| = 2.2361]
Species: lions    Prevalence: 50.00%.
Species: tigers   Prevalence: 33.33%.
Species: bears    Prevalence: 16.67%.

Próxima observação#

Para predizer uma próxima observação, extraímos uma única amostra 10.000 vezes de uma distribuição multinomial. A probabilidade de ver cada espécie é proporcional à obtida na amostragem.

# Draw from the multinomial
next_obs = np.random.multinomial(n = 1, pvals = [0.44,0.33,0.22], size = 10000)

# Data manipulation
next_obs = pd.melt(pd.DataFrame(next_obs, columns = ['Lions', 'Tigers', 'Bears'])).\
            groupby('variable')['value'].\
            value_counts(normalize=True).to_frame().\
             rename(columns = {'value': 'total'}).reset_index()
next_obs = next_obs.loc[next_obs['value'] == 1]

# Bar plot
next_obs.set_index('variable')['total'].plot.bar(figsize = (8, 6));
plt.title('Next Observation Likelihood');
plt.ylabel('Likelihood'); plt.xlabel('');
../_images/97b1a0acafe7aa927fbf31933dc2ac337ff72e3892d88b4da856f80f7f9c8807.png
next_obs.iloc[:, [0, 2]]
variable total
1 Bears 0.2258
3 Lions 0.4476
5 Tigers 0.3266

Qual a probabilidade do próximo animal ser um urso?

Se você tivesse que fazer uma previsão de qual seria o próximo animal observado, qual você escolheria?

Altere os hiperparâmetros para \(\alpha = [0.1, 0.1, 0.1]\) e \(\alpha = [15, 15, 15]\) e compare os resultados obtidos.

(Veja mais análises em https://github.com/WillKoehrsen/probabilistic-programming/blob/master/Estimating Probabilities with Bayesian Inference.ipynb)

Aula 7 - Modelos de regressão#

Programa#

  • Modelos lineares.

  • Regressão múltipla.

  • Regressão multivariada.

  • A qualidade do ajuste.

  • Seleção de modelos.

  • Análise de diagnóstico.

Referências:

  • Draper, N. R., & Smith, H. (1998). Applied regression analysis. 3rd edition. Wiley.

  • James, Gareth et al. (2013) An introduction to statistical learning. New York: Springer.

  • Dobson, A. J.; Barnett, Adrian G. (2018). An introduction to generalized linear models. CRC press.

Modelos de regressão#

Objetivos

Predizer \(Y\) a partir do conhecimento de variáveis em \(X = x\).

Em notação matricial, um modelo linear geral é dado por

\[\large{Y = f(X,\beta)+\epsilon,}\]

em que

  • Y é a variável resposta (vetor de variáveis aleatórias observáveis),

  • X contém variáveis preditoras (matriz conhecida, ou seja, não-aleatória),

  • \(\beta\) é um vetor de parâmetros de interesse, que queremos estimar,

  • \(f\) é uma função das variáveis preditoras e dos parâmetros de interesse,

  • \(\epsilon\) é o erro aleatório (vetor de erros aleatórios não observáveis).

Modelos lineares#

Objetivos

Predizer \(Y\) a partir do conhecimento de variáveis em \(X = x\) utilizando uma função linear dos parâmetros \(\beta\).

Em notação matricial, um modelo linear geral é dado por

\[\large{Y = X\beta+\epsilon,}\]

em que

  • Y é a variável resposta (vetor de variáveis aleatórias observáveis),

  • X contém variáveis preditoras (matriz conhecida, ou seja, não-aleatória),

  • \(\beta\) é um vetor de parâmetros de interesse, que queremos estimar,

  • \(\epsilon\) é o erro aleatório (vetor de erros aleatórios não observáveis).

Modelo de regressão linear simples#

Suponha que são observados os pares \((X_1,Y_1), \ldots, (X_n, Y_n)\) de variável preditora e resposta, respectivamente.

Um modelo linear simples para explicar a variabilidade de \(Y\) usando a variabilidade de \(X\) seria:

\[\large Y_j=\beta_0 + \beta_1 X_j +\epsilon_j, \mbox{ para } j=1,\ldots,n.\]

Nomenclatura:

  • \(Y_j\): \(j\)-ésima observação da variável resposta (dependente, aleatória),

  • \(\beta_0\) e \(\beta_1\): parâmetros desconhecidos e que queremos estimar (fixo e desconhecido),

  • \(X_j\): \(j\)-ésima observação da variável preditora (fixa, ou seja, não aleatória),

  • \(\epsilon_j\): \(j\)-ésimo erro aleatório não observável.

Suposições:

  • \(E(\epsilon_j)=0\) para \(j=1,\ldots,n\),

  • \(Var(\epsilon_j) = \sigma^2\) para \(j=1,\ldots,n\),

  • \(Cov(\epsilon_i,\epsilon_j)=0\) para \(i,j=1,\ldots,n\) e \(i\neq j\).

regressao3.pngregressao2.pngregressao1.png

Interpretação dos parâmetros#

  • \(\beta_0\): valor esperado de \(Y\) quando \(X\) é zero.

  • \(\beta_1\): aumento esperado em \(Y\) quando \(X\) é acrescido de uma unidade.

Modelo de regressão linear múltipla#

Motivação: Deseja-se construir um modelo para explicar

  • \(Y_j\): valor de mercado de uma casa utilizando variáveis explicativas

  • \(X_{1j}\): área

  • \(X_{2j}\): localização

  • \(X_{3j}\): valor da casa no ano anterior

  • \(X_{4j}\): qualidade da construção

Um possível modelo linear (nos parâmetros) seria:

\[\begin{split}\large\begin{array}{c} Y_j=\beta_0 + \beta_1 X_{1j} + \beta_2 X_{2j} + \beta_3 X_{3j}+\beta_4 X_{4j} + \epsilon_j.\\ \end{array} \end{split}\]

Nomenclatura:

  • \(Y_j\): variável resposta (dependente),

  • \(\beta_i\): parâmetros desconhecidos,

  • \(X_{ij}\): variáveis explicativas (covariáveis, variáveis independentes),

  • \(\epsilon_j\): erro aleatório.

Suposições:

  • \(E(\epsilon_j)=0\) para \( j=1,\ldots,n\),

  • \(Var(\epsilon_j) = \sigma^2\) para \( j=1,\ldots,n\),

  • \(Cov(\epsilon_i,\epsilon_j)=0\) para \( i,j=1,\ldots,n\) e \(i\neq j\).

Poderíamos estender esse modelo para \(p\) covariáveis,

\[\begin{split}\large\begin{array}{c} Y_i=\beta_0 + \beta_1 X_{1i} + \beta_2 X_{2i} + \ldots +\beta_p X_{pi} + \epsilon_i, i=1,\ldots,n.\\ \end{array} \end{split}\]

Note que a variável resposta \(Y_i\) é unidimensional.

Poderíamos “empilhar” os dados de \(n\) indivíduos em linhas. Teríamos então matricialmente

\[\begin{split}Y=\left[\begin{array}{c}Y_1\\ Y_2\\ \vdots \\ Y_n\end{array}\right], \ X = \left[\begin{array}{cccc} 1 & X_{11} & \ldots & X_{1p} \\ 1 & X_{21} & \ldots & X_{2p} \\ \vdots & \vdots & \ddots & \vdots\\ 1 & X_{n1} & \ldots & X_{np} \\ \end{array}\right], \beta = \left[\begin{array}{c}\beta_0\\ \beta_1\\\vdots \\ \beta_p\end{array}\right], \ \epsilon = \left[\begin{array}{c}\epsilon_1\\ \epsilon_2\\\vdots \\ \epsilon_n\end{array}\right] \end{split}\]

ou seja,

\[Y_{n\times 1} = X_{n\times (p+1)} \beta_{(p+ 1)\times 1} + \epsilon_{n\times 1}. \]

Interpretação dos parâmetros#

  • \(\beta_0\): valor esperado de \(Y\) quando \(X_{1i}, X_{2i}, \ldots, X_{pi}\) são todas zero.

  • \(\beta_k\): aumento esperado em \(Y\) quando \(X_k\) é acrescido de uma unidade e todas as outras são mantidas fixadas, \(k=1,\ldots,p\).

Estimação dos parâmetros#

Alguns métodos podem ser usados para estimar os parâmetros, por exemplo

  • Método de mínimos quadrados ordinários (EMQ ou MQO)

  • Método de máxima verossimilhança (EMV)

No modelo linear geral $\(\large Y = X \beta + \epsilon. \)$

com as suposições

  • \(E( \epsilon) = {0}\),

  • \(Var( \epsilon) = \sigma^2 I\),

o estimador de mínimos quadrados que minimiza a soma de quadrados dos resíduos, é dado por

\[\widehat{\beta} = (X ^{\top} X)^{-1} X ^{\top} Y.\]

Se \(\epsilon\sim N({0},\sigma^2 I)\), então

o estimador de máxima verossimilhança de \(\beta\) é dado (também) por

\[\widehat{\beta} = (X^{\top} X)^{-1} X^\top Y.\]

Nesse caso,

\[\widehat{\beta} \sim N\left(\beta, \sigma^2 (X^\top X)^{-1}\right)\]

e é comum estimar \(\sigma^2\) com

\[\widehat{\sigma}^2 = MSE.\]

Observação

O EMV de \(\beta\) é não-viesado e consistente, que são boas propriedades estatísticas.

Valor ajustado de \(Y\)#

O valor ajustado de \(Y\), para um determinado \(X = x\) é obtido fazendo

\[\large \hat{Y} = X {\hat{\beta}}.\]

O erro quadrático médio, MSE, é usado para estimar \(\sigma^2\), fazendo

\[\large \hat{\sigma}^2 = \displaystyle{\frac{SQE}{n-p}} = \displaystyle{\frac{\displaystyle\sum_{i=1}^{n}(Y-\hat{Y})^\top(Y-\hat{Y}) }{n-p}} \]

Coeficiente de determinação do modelo#

O coeficiente de determinação, ou coeficiente de explicação do modelo, é dado por

\[R^2 = 1 - \displaystyle {\frac{SQE}{SQT}},\]

em que \(SQT = Y^\top Y - \displaystyle\frac{1}{n}Y^\top \mathbb{1}^\top \mathbb{1} Y\), em que \(\mathbb{1}\) indica um vetor de uns de mesma dimensão de \(Y\).

Para levar em conta o aumento da explicação da variabilidade da resposta quando aumentamos o número de covariáveis, é comum considerar o coeficiente de determinação do modelo ajustado:

\[R^2_{ajustado} = 1 - \displaystyle {\frac{n-1}{n-p}\frac{SQE}{SQT}}.\]

Tanto \(R^2\) quanto \(R^2_{ajustado}\) estão entre 0 e 1, e pode ser usado como um indício de qualidade do ajuste, quanto maior o coeficiente de determinação, melhor é o modelo linear.

Modelo de regressão linear multivariada#

Considere agora que, para cada indivíduo, sejam observadas \(m\) variáveis respostas, e que cada uma delas tenha uma relação linear com as p covariáveis.

Assim, teriamos \(m\) modelos de regressão:

\[\begin{split}\begin{array}{c} Y_1=\beta_{01} + \beta_{11} X_1 + \beta_{21} X_2 + \beta_{31} X_3+\ldots+\beta_{p1} X_p+\epsilon_{1}\\ Y_2=\beta_{02} + \beta_{12} X_1 + \beta_{22} X_2 + \beta_{32} X_3+\ldots+\beta_{p2} X_p+\epsilon_{2}\\ \vdots\\ Y_m=\beta_{0m} + \beta_{1m} X_1 + \beta_{2m} X_2 + \beta_{3m} X_3+\ldots+\beta_{pm} X_p+\epsilon_{m}\\ \end{array} \end{split}\]

Para cada um dos \(n\) indivíduos, vamos observar as \(m\) variáveis resposta e as \(p\) covariáveis. Assim, podemos definir um modelo de regressão multivariado

\[Y_{n\times m} = X_{n\times (p+1)} \beta_{(p+1)\times m} + \epsilon_{n\times m}\]

em que

\[\begin{split}Y=\left[\begin{array}{cccc} Y_{11} & Y_{12} & \ldots & Y_{1m} \\ Y_{21} & Y_{22} & \ldots & Y_{2m} \\ \vdots & \vdots & \ddots & \vdots\\ Y_{n1} & Y_{n2} & \ldots & Y_{nm} \\ \end{array}\right], \ X = \left[\begin{array}{cccc} 1 & X_{11} & \ldots & X_{1p} \\ 1 & X_{21} & \ldots & X_{2p} \\ \vdots & \vdots & \ddots & \vdots\\ 1 & X_{n1} & \ldots & X_{np} \\ \end{array}\right],\end{split}\]
\[\begin{split}\beta = \left[\begin{array}{cccc} \beta_{01} & \beta_{02} & \ldots & \beta_{0m}\\ \beta_{11} & \beta_{12} & \ldots & \beta_{1m}\\ \vdots & \vdots & \ddots & \vdots\\ \beta_{p1} & \beta_{p2} & \ldots & \beta_{pm}\\ \end{array}\right], \ \epsilon = \left[\begin{array}{cccc} \epsilon_{11} & \epsilon_{12} & \ldots & \epsilon_{1m} \\ \epsilon_{21} & \epsilon_{22} & \ldots & \epsilon_{2m} \\ \vdots & \vdots & \ddots & \vdots\\ \epsilon_{n1} & \epsilon_{n2} & \ldots & \epsilon_{nm} \end{array}\right] \end{split}\]

Material complementar#

Para as suposições e demais desenvolvimentos no modelo de regressão linear multivariado, veja a aula de Regressão multivariada em https://youtu.be/9QIh71MQ2xQ

Análise de diagnóstico#

Os resíduos contém indicativos de adequabilidade das suposições do modelo

Os resíduos ordinários do modelo são dados por

\[\large e = Y-\widehat{Y}.\]

É comum construir gráficos dos resíduos ordinários contra a ordem das observações, os valores ajustados \(\widehat{Y}\) e \(X_i\), para algumas das variáveis preditoras de interesse.

Espera-se que os resíduos sejam aleatoriamente distribuídos em torno de zero.

Alguns padrões de resíduos são ilustrados a seguir:

residuos3.pngresiduos2.pngresiduos1.png

Existem algumas propostas para a padronização de resíduos:

Temos que

\[e = Y-\widehat{Y} = Y - X (X ^{\top} X)^{-1} X ^{\top} Y = (I - X (X ^{\top} X)^{-1} X ^{\top}) Y = (I-H) Y,\]

em que \(H = X (X ^{\top} X)^{-1} X ^{\top}\) é a matriz hat (matriz chapéu). Seja \(h_{ii}\) o \(i\)-ésimo elemento da diagonal de \(H\).

Pode-se mostrar que

\[Var(e_i) = (1-h_{ii})\sigma^2\]

Assim, podemos definir dois novos resíduos:

  • Resíduo Studentizado internamente:

\[s_i = \displaystyle\frac{e_i}{s\sqrt{1-h_{ii}}},\]

em que \(s\) é uma estimativa de \(\sigma\), usualmente a raiz do MSE.

  • Resíduo Studentizado externamente

\[t_i = \displaystyle\frac{e_i}{s(i)\sqrt{1-h_{ii}}},\]

em que \(s(i)\) é uma estimativa para \(\sigma^2\) sem a observação i.

Observação: Com \(h_{ii}\) é possível identificar pontos de alavanca, que são outliers no espaço dos X e não necessariamente são ponto influentes, ou seja, não necessariamente mudam a inferência do modelo.

Existem técnicas para identificar pontos influentes, como DFFITS e Distância de Cook, que veremos na prática.

Ilustração de resíduos e pontos de alavanca#

Conjuntos de dados de Anscombe: https://pt.wikipedia.org/wiki/Quarteto_de_Anscombe

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
anscombe = sns.load_dataset("anscombe")

sns.lmplot(x="x", y="y", data=anscombe.query("dataset == 'IV'"),
           robust=True, ci=None, scatter_kws={"s": 80});
../_images/b6b431138879cc59c6e87acc13cf32d5d8ef81a74c474db71bb1b4a202ea491b.png
# Gráficos de diagnóstico para o modelo linear simples com estimadores MQO

# Fonte: https://stackoverflow.com/questions/46607831/python-linear-regression-diagnostic-plots-similar-to-r
# Fonte: https://stackoverflow.com/questions/46304514/access-standardized-residuals-cooks-values-hatvalues-leverage-etc-easily-i/55764402#55764402

from matplotlib import pyplot as plt
from pandas.core.frame import DataFrame
import scipy.stats as stats
import statsmodels.api as sm


def linear_regression(df: DataFrame) -> DataFrame:
    """Perform a univariate regression and store results in a new data frame.

    Args:
        df (DataFrame): orginal data set with x and y.

    Returns:
        DataFrame: another dataframe with raw data and results.
    """
    mod = sm.OLS(endog=df['y'], exog=df['x']).fit()
    influence = mod.get_influence()

    res = df.copy()
    res['resid'] = mod.resid
    res['fittedvalues'] = mod.fittedvalues
    res['resid_std'] = mod.resid_pearson
    res['leverage'] = influence.hat_matrix_diag
    return res


def plot_diagnosis(df: DataFrame):
    fig, axes = plt.subplots(nrows=2, ncols=2)
    plt.style.use('seaborn')

    # Residual against fitted values.
    df.plot.scatter(
        x='fittedvalues', y='resid', ax=axes[0, 0]
    )
    axes[0, 0].axhline(y=0, color='grey', linestyle='dashed')
    axes[0, 0].set_xlabel('Fitted Values')
    axes[0, 0].set_ylabel('Residuals')
    axes[0, 0].set_title('Residuals vs Fitted')

    # qqplot
    sm.qqplot(
        df['resid'], dist=stats.t, fit=True, line='45',
        ax=axes[0, 1], c='#4C72B0'
    )
    axes[0, 1].set_title('Normal Q-Q')

    # The scale-location plot.
    df.plot.scatter(
        x='fittedvalues', y='resid_std', ax=axes[1, 0]
    )
    axes[1, 0].axhline(y=0, color='grey', linestyle='dashed')
    axes[1, 0].set_xlabel('Fitted values')
    axes[1, 0].set_ylabel('Sqrt(|standardized residuals|)')
    axes[1, 0].set_title('Scale-Location')

    # Standardized residuals vs. leverage
    df.plot.scatter(
        x='leverage', y='resid_std', ax=axes[1, 1]
    )
    axes[1, 1].axhline(y=0, color='grey', linestyle='dashed')
    axes[1, 1].set_xlabel('Leverage')
    axes[1, 1].set_ylabel('Sqrt(|standardized residuals|)')
    axes[1, 1].set_title('Residuals vs Leverage')

    plt.tight_layout()
    plt.show()
df = data=anscombe.query("dataset == 'IV'")
df = df.drop('dataset', axis=1)
df = linear_regression(df)
plot_diagnosis(df)
C:\Users\imikemori\Anaconda3\lib\site-packages\statsmodels\graphics\gofplots.py:1045: UserWarning: color is redundantly defined by the 'color' keyword argument and the fmt string "b" (-> color=(0.0, 0.0, 1.0, 1)). The keyword argument will take precedence.
  ax.plot(x, y, fmt, **plot_style)
../_images/f5f0204157bf71c7ea349004cb91dade923c91a299bc4f0e9241be68c72b952d.png

Seleção de modelos#

Algumas formas de avaliar e selecionar modelos, além da análise de diagnóstico, são

Métricas para avaliar a qualidade do ajuste#

Erro absoluto médio (EAM, MAE)#

O erro quadrático médio (EQM) ou mean absolute error (MAE) é a média do valor absoluto dos erros.

\(\mbox{MAE} = \displaystyle\frac{1}{n} \displaystyle\sum_{j=1}^{n}|y_j - \widehat{y}_j|\)

Erro quadrático médio (EQM, MSE)#

O erro quadrático médio (EQM) ou mean squared error (MSE) é a média dos erros quadráticos.

\(\mbox{MSE} = \displaystyle\frac{1}{n} \displaystyle\sum_{j=1}^{n}(y_j - \widehat{y}_j)^2\)

Raiz do erro quadrático médio (REQM, RMSE)#

A raiz do erro quadrático médio (REQM) ou root mean squared error (RMSE) é a raiz da média dos erros quadráticos.

\(\mbox{RMSE} = \displaystyle\sqrt{\frac{1}{n} \displaystyle\sum_{j=1}^{n}(y_j - \widehat{y}_j)^2}\)

Aplicação#

Suponha que desejamos predizer o valor de venda de uma casa utilizando variáveis preditoras como número de quartos, número de banheiros, tamanho da sala, número de andares, entre outras.

Fonte e alguns desenvolvimentos adicionais: ver https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices

!pip install folium
Collecting folium
  Downloading folium-0.15.1-py2.py3-none-any.whl (97 kB)
     ---------------------------------------- 97.0/97.0 kB 2.7 MB/s eta 0:00:00
Collecting xyzservices
  Downloading xyzservices-2023.10.1-py3-none-any.whl (56 kB)
     ---------------------------------------- 56.3/56.3 kB 2.9 MB/s eta 0:00:00
Collecting branca>=0.6.0
  Downloading branca-0.7.0-py3-none-any.whl (25 kB)
Requirement already satisfied: numpy in c:\users\imikemori\anaconda3\lib\site-packages (from folium) (1.24.4)
Requirement already satisfied: jinja2>=2.9 in c:\users\imikemori\anaconda3\lib\site-packages (from folium) (2.11.3)
Requirement already satisfied: requests in c:\users\imikemori\anaconda3\lib\site-packages (from folium) (2.28.1)
Requirement already satisfied: MarkupSafe>=0.23 in c:\users\imikemori\anaconda3\lib\site-packages (from jinja2>=2.9->folium) (2.0.1)
Requirement already satisfied: charset-normalizer<3,>=2 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (2.0.4)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (2022.9.14)
Requirement already satisfied: idna<4,>=2.5 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (3.3)
Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\imikemori\anaconda3\lib\site-packages (from requests->folium) (1.26.11)
Installing collected packages: xyzservices, branca, folium
Successfully installed branca-0.7.0 folium-0.15.1 xyzservices-2023.10.1
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution -rotobuf (c:\users\imikemori\anaconda3\lib\site-packages)
WARNING: Ignoring invalid distribution - (c:\users\imikemori\anaconda3\lib\site-packages)
import numpy as np
import pandas as pd 
from sklearn.model_selection import train_test_split
from sklearn import linear_model
from sklearn.neighbors import KNeighborsRegressor
from sklearn.preprocessing import PolynomialFeatures
from sklearn import metrics
from sklearn.model_selection import cross_val_score


import matplotlib.pyplot as plt
import seaborn as sns
from mpl_toolkits.mplot3d import Axes3D
import folium
from folium.plugins import HeatMap
%matplotlib inline
import warnings
warnings.filterwarnings('ignore')

evaluation = pd.DataFrame({'Model': [],
                           'Details':[],
                           'Root Mean Squared Error (RMSE)':[],
                           'R-squared (training)':[],
                           'Adjusted R-squared (training)':[],
                           'R-squared (test)':[],
                           'Adjusted R-squared (test)':[],
                           '5-Fold Cross Validation':[]})

# Faça a leitura dos dados localmente 
#df = pd.read_csv('/hdd/MBA/ECD/Data/kc_house_data.csv')

# Ou faça a leitura direto do github
df = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/kc_house_data.csv')
df.head()
id date price bedrooms bathrooms sqft_living sqft_lot floors waterfront view ... grade sqft_above sqft_basement yr_built yr_renovated zipcode lat long sqft_living15 sqft_lot15
0 7129300520 20141013T000000 221900.0 3 1.00 1180 5650 1.0 0 0 ... 7 1180.0 0 1955 0 98178 47.5112 -122.257 1340 5650
1 6414100192 20141209T000000 538000.0 3 2.25 2570 7242 2.0 0 0 ... 7 2170.0 400 1951 1991 98125 47.7210 -122.319 1690 7639
2 5631500400 20150225T000000 180000.0 2 1.00 770 10000 1.0 0 0 ... 6 770.0 0 1933 0 98028 47.7379 -122.233 2720 8062
3 2487200875 20141209T000000 604000.0 4 3.00 1960 5000 1.0 0 0 ... 7 1050.0 910 1965 0 98136 47.5208 -122.393 1360 5000
4 1954400510 20150218T000000 510000.0 3 2.00 1680 8080 1.0 0 0 ... 8 1680.0 0 1987 0 98074 47.6168 -122.045 1800 7503

5 rows × 21 columns

df.describe()
id price bedrooms bathrooms sqft_living sqft_lot floors waterfront view condition grade sqft_above sqft_basement yr_built yr_renovated zipcode lat long sqft_living15 sqft_lot15
count 2.161300e+04 2.161300e+04 21613.000000 21613.000000 21613.000000 2.161300e+04 21613.000000 21613.000000 21613.000000 21613.000000 21613.000000 21611.000000 21613.000000 21613.000000 21613.000000 21613.000000 21613.000000 21613.000000 21613.000000 21613.000000
mean 4.580302e+09 5.400881e+05 3.370842 2.114757 2079.899736 1.510697e+04 1.494309 0.007542 0.234303 3.409430 7.656873 1788.396095 291.509045 1971.005136 84.402258 98077.939805 47.560053 -122.213896 1986.552492 12768.455652
std 2.876566e+09 3.671272e+05 0.930062 0.770163 918.440897 4.142051e+04 0.539989 0.086517 0.766318 0.650743 1.175459 828.128162 442.575043 29.373411 401.679240 53.505026 0.138564 0.140828 685.391304 27304.179631
min 1.000102e+06 7.500000e+04 0.000000 0.000000 290.000000 5.200000e+02 1.000000 0.000000 0.000000 1.000000 1.000000 290.000000 0.000000 1900.000000 0.000000 98001.000000 47.155900 -122.519000 399.000000 651.000000
25% 2.123049e+09 3.219500e+05 3.000000 1.750000 1427.000000 5.040000e+03 1.000000 0.000000 0.000000 3.000000 7.000000 1190.000000 0.000000 1951.000000 0.000000 98033.000000 47.471000 -122.328000 1490.000000 5100.000000
50% 3.904930e+09 4.500000e+05 3.000000 2.250000 1910.000000 7.618000e+03 1.500000 0.000000 0.000000 3.000000 7.000000 1560.000000 0.000000 1975.000000 0.000000 98065.000000 47.571800 -122.230000 1840.000000 7620.000000
75% 7.308900e+09 6.450000e+05 4.000000 2.500000 2550.000000 1.068800e+04 2.000000 0.000000 0.000000 4.000000 8.000000 2210.000000 560.000000 1997.000000 0.000000 98118.000000 47.678000 -122.125000 2360.000000 10083.000000
max 9.900000e+09 7.700000e+06 33.000000 8.000000 13540.000000 1.651359e+06 3.500000 1.000000 4.000000 5.000000 13.000000 9410.000000 4820.000000 2015.000000 2015.000000 98199.000000 47.777600 -121.315000 6210.000000 871200.000000
fig, ax = plt.subplots(figsize=(12,12))
sns.heatmap(df.drop(['id'], axis=1).corr(),annot=True, square=True, cmap="BuGn")

plt.title('Matriz de correlações de Pearson',fontsize=25)
Text(0.5, 1.0, 'Matriz de correlações de Pearson')
../_images/a4335a082c352538a1d9801eacdcd4b1bb9a227356d9c2d53d1b8a641ca22607.png

Modelo linear simples#

from statsmodels.formula.api import ols

#Ajusta o modelo de regressão linear simples para o preço das casas
mod = ols('price~sqft_living',data=df)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                  price   R-squared:                       0.493
Model:                            OLS   Adj. R-squared:                  0.493
Method:                 Least Squares   F-statistic:                 2.100e+04
Date:                Wed, 17 Jan 2024   Prob (F-statistic):               0.00
Time:                        01:54:36   Log-Likelihood:            -3.0027e+05
No. Observations:               21613   AIC:                         6.005e+05
Df Residuals:                   21611   BIC:                         6.006e+05
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
===============================================================================
                  coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------
Intercept   -4.358e+04   4402.690     -9.899      0.000   -5.22e+04    -3.5e+04
sqft_living   280.6236      1.936    144.920      0.000     276.828     284.419
==============================================================================
Omnibus:                    14832.490   Durbin-Watson:                   1.983
Prob(Omnibus):                  0.000   Jarque-Bera (JB):           546444.713
Skew:                           2.824   Prob(JB):                         0.00
Kurtosis:                      26.977   Cond. No.                     5.63e+03
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 5.63e+03. This might indicate that there are
strong multicollinearity or other numerical problems.

Modelo ajustado

\(\hat{Y}_i = -43580 + 280.62 \mbox{ sqft_living}\)

Modelo linear múltiplo#

X = df[['bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront','view','condition','grade']].values

y = df['price'].values
from statsmodels.formula.api import ols

#Ajusta o modelo de regressão linear múltipla para o preço das casas
modelo = ols('price ~ bedrooms + bathrooms + sqft_living + sqft_lot + floors + waterfront + view + condition + grade',data=df)

res = modelo.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                  price   R-squared:                       0.605
Model:                            OLS   Adj. R-squared:                  0.605
Method:                 Least Squares   F-statistic:                     3673.
Date:                Wed, 17 Jan 2024   Prob (F-statistic):               0.00
Time:                        01:54:36   Log-Likelihood:            -2.9757e+05
No. Observations:               21613   AIC:                         5.952e+05
Df Residuals:                   21603   BIC:                         5.952e+05
Df Model:                           9                                         
Covariance Type:            nonrobust                                         
===============================================================================
                  coef    std err          t      P>|t|      [0.025      0.975]
-------------------------------------------------------------------------------
Intercept   -6.827e+05   1.73e+04    -39.509      0.000   -7.17e+05   -6.49e+05
bedrooms    -3.367e+04   2159.240    -15.594      0.000   -3.79e+04   -2.94e+04
bathrooms   -1.142e+04   3449.874     -3.309      0.001   -1.82e+04   -4653.384
sqft_living   196.3657      3.454     56.855      0.000     189.596     203.135
sqft_lot       -0.3462      0.039     -8.931      0.000      -0.422      -0.270
floors      -1.312e+04   3575.901     -3.669      0.000   -2.01e+04   -6109.502
waterfront   5.783e+05   1.99e+04     29.128      0.000    5.39e+05    6.17e+05
view         6.327e+04   2348.558     26.939      0.000    5.87e+04    6.79e+04
condition    5.499e+04   2521.782     21.805      0.000       5e+04    5.99e+04
grade        1.006e+05   2231.983     45.064      0.000    9.62e+04    1.05e+05
==============================================================================
Omnibus:                    15533.601   Durbin-Watson:                   1.983
Prob(Omnibus):                  0.000   Jarque-Bera (JB):           900296.321
Skew:                           2.871   Prob(JB):                         0.00
Kurtosis:                      34.093   Cond. No.                     5.59e+05
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 5.59e+05. This might indicate that there are
strong multicollinearity or other numerical problems.

Métricas#

# R2 ajustado de
# https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices

def adjustedR2(r2,n,k):
    return r2-(k-1)/(n-k)*(1-r2)
# Fonte: https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices

train_data,test_data = train_test_split(df,train_size = 0.8,random_state=3)

features = ['bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront', 'view', 'condition', 'grade']
complex_model_1 = linear_model.LinearRegression()
complex_model_1.fit(train_data[features],train_data['price'])

print('Intercept: {}'.format(complex_model_1.intercept_))
print('Coefficients: {}'.format(complex_model_1.coef_))

pred = complex_model_1.predict(test_data[features])
rmsecm = float(format(np.sqrt(metrics.mean_squared_error(test_data['price'],pred)),'.3f'))
rtrcm = float(format(complex_model_1.score(train_data[features],train_data['price']),'.3f'))

artrcm = float(format(adjustedR2(complex_model_1.score(train_data[features],train_data['price']),train_data.shape[0],len(features)),'.3f'))
rtecm = float(format(complex_model_1.score(test_data[features],test_data['price']),'.3f'))
artecm = float(format(adjustedR2(complex_model_1.score(test_data[features],test_data['price']),test_data.shape[0],len(features)),'.3f'))
cv = float(format(cross_val_score(complex_model_1,df[features],df['price'],cv=5).mean(),'.3f'))

r = evaluation.shape[0]
evaluation.loc[r] = ['Multiple Regression-1','selected features',rmsecm,rtrcm,artrcm,rtecm,artecm,cv]
evaluation.sort_values(by = '5-Fold Cross Validation', ascending=False)
Intercept: -682602.5354521422
Coefficients: [-3.37785908e+04 -1.09025075e+04  1.99448654e+02 -3.50854472e-01
 -1.48240739e+04  5.45051297e+05  6.50285793e+04  5.58998443e+04
  9.95878298e+04]
Model Details Root Mean Squared Error (RMSE) R-squared (training) Adjusted R-squared (training) R-squared (test) Adjusted R-squared (test) 5-Fold Cross Validation
0 Multiple Regression-1 selected features 221680.867 0.602 0.602 0.617 0.616 0.601

Seleção de variáveis (feature selection)#

X = df[['bedrooms','bathrooms','sqft_living','sqft_lot','floors','waterfront','view','condition','grade']]

y = df['price']


X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, test_size=0.2, random_state = 1)
# https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices

# load and summarize the dataset
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_regression
from sklearn.feature_selection import mutual_info_regression
from matplotlib import pyplot


# feature selection
def select_features(X_treino, y_treino, X_teste):
	# configure to select all features
	fs = SelectKBest(score_func=mutual_info_regression, k='all')
	# learn relationship from treinoing data
	fs.fit(X_treino, y_treino)
	# transform treino input data
	X_treino_fs = fs.transform(X_treino)
	# transform teste input data
	X_teste_fs = fs.transform(X_teste)
	return X_treino_fs, X_teste_fs, fs
 
# split into treino and teste sets
X_treino, X_teste, y_treino, y_teste = train_test_split(X, y, test_size=0.2, random_state=1)

# feature selection
X_treino_fs, X_teste_fs, fs = select_features(X_treino, y_treino, X_teste)

# what are scores for the features
for i in range(len(fs.scores_)):
	print('Feature %d: %f' % (i, fs.scores_[i]))
    
# plot the scores
pyplot.bar([i for i in range(len(fs.scores_))], fs.scores_)
pyplot.show()
Feature 0: 0.072447
Feature 1: 0.204750
Feature 2: 0.348548
Feature 3: 0.063075
Feature 4: 0.075901
Feature 5: 0.014150
Feature 6: 0.058074
Feature 7: 0.009088
Feature 8: 0.339902
../_images/801216ce64d5af91af2fa826c9e09899c1d285c304d4cd82d9122d554c4d49e1.png
# Fonte: https://www.kaggle.com/burhanykiyakoglu/predicting-house-prices

train_data,test_data = train_test_split(df,train_size = 0.8,random_state=3)

features = ['sqft_living','grade']
complex_model_1 = linear_model.LinearRegression()
complex_model_1.fit(train_data[features],train_data['price'])

print('Intercept: {}'.format(complex_model_1.intercept_))
print('Coefficients: {}'.format(complex_model_1.coef_))

pred = complex_model_1.predict(test_data[features])
rmsecm = float(format(np.sqrt(metrics.mean_squared_error(test_data['price'],pred)),'.3f'))
rtrcm = float(format(complex_model_1.score(train_data[features],train_data['price']),'.3f'))

artrcm = float(format(adjustedR2(complex_model_1.score(train_data[features],train_data['price']),train_data.shape[0],len(features)),'.3f'))
rtecm = float(format(complex_model_1.score(test_data[features],test_data['price']),'.3f'))
artecm = float(format(adjustedR2(complex_model_1.score(test_data[features],test_data['price']),test_data.shape[0],len(features)),'.3f'))
cv = float(format(cross_val_score(complex_model_1,df[features],df['price'],cv=5).mean(),'.3f'))

r = evaluation.shape[0]
evaluation.loc[r] = ['Multiple Regression-3','selected features',rmsecm,rtrcm,artrcm,rtecm,artecm,cv]
evaluation.sort_values(by = '5-Fold Cross Validation', ascending=False)
Intercept: -598022.8027257536
Coefficients: [  186.877963  97878.7708715]
Model Details Root Mean Squared Error (RMSE) R-squared (training) Adjusted R-squared (training) R-squared (test) Adjusted R-squared (test) 5-Fold Cross Validation
0 Multiple Regression-1 selected features 221680.867 0.602 0.602 0.617 0.616 0.601
1 Multiple Regression-3 selected features 242366.385 0.533 0.533 0.542 0.542 0.533

Análise de resíduos#

# Ajusta o modelo de regressão linear múltipla para o preço das casas com duas preditoras
modelo = ols('price ~ sqft_living + grade',data=df)
res = modelo.fit()

# valores ajustados de E(Y)
ypred=res.fittedvalues

# resíduo=observado-ajustado
residuo = res.resid

# objeto para a análise de pontos influentes
infl = res.get_influence()

# diagonal da matriz hat
hii = infl.hat_matrix_diag

# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal

# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external

# DFFITS
(dffits,p) = infl.dffits

# Distância de Cook
(cook,p) = infl.cooks_distance
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')

for ax in fig.get_axes():
    ax.label_outer()
../_images/bc55a1f68f1410afad7d7a6da9301343e05ce411536e8b12f7ecabb8b318c263.png
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(df.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
#ax1.hlines(0,xmin=1,xmax=102,color='gray')

ax2.scatter(df.index, cook)
ax2.set_ylabel('distância de Cook')

ax3.scatter(df.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')

for ax in fig.get_axes():
    ax.label_outer()
../_images/c1c0d5e32d000f76844068732df7a62fcff7b4f83a69401a0b41b6c7106ee132.png
# Verificando a suposição de distribuição Normal dos resíduos

stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
../_images/4d933c57b6cc660ead769774f78d77e51f9631890f2e0fa72679352667016aa1.png

Transformação em Y#

Transformação de Box Cox: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.boxcox.html

stats.boxcox(df['price'])
(array([4.0334758 , 4.07834265, 4.02144527, ..., 4.06460556, 4.06434971,
        4.05395248]),
 -0.23401853749997595)
df['price_transformado'] = (pow(df['price'],-0.234) - 1)/(-0.234)
# Ajusta o modelo de regressão linear múltipla para o preço das casas com duas preditoras
modelo = ols('price_transformado ~ sqft_living + grade',data=df)
res = modelo.fit()

# valores preditos de E(Y)
ypred=res.fittedvalues

# resíduo=observado-ajustado
residuo = res.resid

# objeto para a análise de pontos influentes
infl = res.get_influence()

# diagonal da matriz hat
hii = infl.hat_matrix_diag

# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal

# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external

# DFFITS
(dffits,p) = infl.dffits

# Distância de Cook
(cook,p) = infl.cooks_distance
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
#ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')

ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')

ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')

ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')

for ax in fig.get_axes():
    ax.label_outer()
../_images/4112aec2ece47d415054cc94e528ce73ce1ef9183e47d90d7006d0f29715c2ca.png
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(df.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')

#ax2.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(df.index, cook)
ax2.set_ylabel('distância de Cook')

ax3.scatter(df.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')

for ax in fig.get_axes():
    ax.label_outer()
../_images/f539ad89503b6079ef77ce37e5e8523a9c14a6fbb91fa6daafe95fd378ec779c.png
# Verificando a suposição de distribuição Normal dos resíduos

stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
../_images/feba5dd6602b2dbe9dda1fad9a6d3c23501c14980523761d5fbb09084ab5a032.png
#!pip install plotly
# x and y given as DataFrame columns
import plotly.express as px

fig = px.scatter(x = df.index, y=cook)
fig.show()
df['grade'].describe()
count    21613.000000
mean         7.656873
std          1.175459
min          1.000000
25%          7.000000
50%          7.000000
75%          8.000000
max         13.000000
Name: grade, dtype: float64
df.iloc[12777,:]

#2.280.000
id                         1225069038
date                  20140505T000000
price                       2280000.0
bedrooms                            7
bathrooms                         8.0
sqft_living                     13540
sqft_lot                       307752
floors                            3.0
waterfront                          0
view                                4
condition                           3
grade                              12
sqft_above                     9410.0
sqft_basement                    4130
yr_built                         1999
yr_renovated                        0
zipcode                         98053
lat                           47.6675
long                         -121.986
sqft_living15                    4850
sqft_lot15                     217800
price_transformado             4.1345
Name: 12777, dtype: object
sns.boxplot(df['sqft_living'])
<AxesSubplot:xlabel='sqft_living'>
../_images/d5cc0dd16c09ff69b512464318deb9286f2762730f265847ee34b12cf45538b3.png
px.scatter(y = df['price'], x=df['sqft_living'])
px.scatter(y = df['price_transformado'], x=df['sqft_living'])
res.params
Intercept      3.983499
sqft_living    0.000009
grade          0.008750
dtype: float64
res.predict()
array([4.055955  , 4.06915421, 4.04331154, ..., 4.05443567, 4.06869342,
       4.05443567])
X = df[['sqft_living', 'grade']].values.reshape(-1,2)
Y = df['price']

x = X[:, 0]
y = X[:, 1]
z = Y
# Visualização do modelo de regressão múltipla em Python

## Fonte: https://aegis4048.github.io/mutiple_linear_regression_and_visualization_in_python


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import linear_model
from mpl_toolkits.mplot3d import Axes3D

######################################## Preparação dos dados #########################################


X = df[['sqft_living', 'grade']].values.reshape(-1,2)
Y = df['price_transformado']

######################## Preparação para a visualização ###############################

x = X[:, 0]
y = X[:, 1]
z = Y

x_pred = np.linspace(290, 13540, 30)   # grade de valores para x
y_pred = np.linspace(1, 13, 30)  # grade de valores para y
xx_pred, yy_pred = np.meshgrid(x_pred, y_pred)
model_viz = np.array([xx_pred.flatten(), yy_pred.flatten()]).T

################################################ Treinamento do modelo #############################################

ols = linear_model.LinearRegression()
model = ols.fit(X, Y)
predicted = model.predict(model_viz)

############################################## Avaliação ############################################

r2 = model.score(X, Y)

############################################## Plota o gráfico ################################################

plt.style.use('default')

fig = plt.figure(figsize=(12, 4))

ax1 = fig.add_subplot(131, projection='3d')
ax2 = fig.add_subplot(132, projection='3d')


axes = [ax1, ax2]

for ax in axes:
    ax.plot(x, y, z, color='k', zorder=15, linestyle='none', marker='o', alpha=0.5)
    ax.scatter(xx_pred.flatten(), yy_pred.flatten(), predicted, facecolor=(0,0,0,0), s=20, edgecolor='#70b3f0')
    ax.set_xlabel('sqft_living', fontsize=12)
    ax.set_ylabel('grade', fontsize=12)
    ax.set_zlabel('price_transformado', fontsize=12)
    ax.locator_params(nbins=4, axis='x')
    ax.locator_params(nbins=5, axis='x')


ax1.view_init(elev=20, azim=-10)
ax2.view_init(elev=0, azim=290)


fig.suptitle('$R^2 = %.2f$' % r2, fontsize=20)

fig.tight_layout()
../_images/03aaa750b1cd0dd661334f253600301ae3cf6a552f5dc671ad3716d1167b21d2.png
for ii in np.arange(0, 180, 1):
    ax1.view_init(elev=20, azim=(2*ii))
    ax2.view_init(elev=0, azim=(2*ii))
    fig.savefig('gif_image%d.png' % ii)

Métricas com bases de treino e teste#

# Ajusta o modelo de regressão linear múltipla para o preço das casas com duas preditoras
from statsmodels.formula.api import ols

modelo = ols('price ~ sqft_living + grade', data=train_data)
res = modelo.fit()
previsao = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error

mean_absolute_error(previsao, y_teste)
168694.27817451468
mean_squared_error(previsao, y_teste)
79633647247.60545

Exercício

É possível melhorar esse modelo?

Testar outros métodos para a seleção de variáveis.

Prática 1#

Modelos Lineares#

Exercício: dados Prestige#

Fontes:

O arquivo Prestige.csv contém 102 observações e 7 variáveis. A descrição das variáveis no conjunto de dados é a seguinte:

  • occupation: profissão

  • education: número médio de anos de estudo dos titulares da profissão.

  • income: A renda média dos ocupantes ocupacionais, em dólares.

  • women: a porcentagem de mulheres na ocupação.

  • prestige: a classificação média de prestígio para a ocupação.

  • census: o código da ocupação utilizado na pesquisa.

  • type: profissional e gerencial (prof), colarinho branco (wc), colarinho azul (bc) ou ausente (NA) (Fox e Weisberg 2011)

Ajuste um modelo de regressão linear múltiplo para compreender a associação entre o prestígio e as preditoras renda, educação e mulheres. Verifique se o modelo é adequado.

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
import statistics
import pandas as pd
from scipy import stats
from scipy.stats import norm

# Dados Prestige - Leitura dos dados a partir de uma pasta local
#pkgdir = '/hdd/MBA/ECD/Data'
#dados = pd.read_csv(f'{pkgdir}/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')

# Leitura dos dados direto do github
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')

dados.head()
occupation education income women prestige census type
0 gov.administrators 13.11 12351 11.16 68.8 1113 prof
1 general.managers 12.26 25879 4.02 69.1 1130 prof
2 accountants 12.77 9271 15.70 63.4 1171 prof
3 purchasing.officers 11.42 8865 9.11 56.8 1175 prof
4 chemists 14.62 8403 11.68 73.5 2111 prof
dados.describe()
education income women prestige census
count 102.000000 102.000000 102.000000 102.000000 102.000000
mean 10.738039 6797.901961 28.979020 46.833333 5401.774510
std 2.728444 4245.922227 31.724931 17.204486 2644.993215
min 6.380000 611.000000 0.000000 14.800000 1113.000000
25% 8.445000 4106.000000 3.592500 35.225000 3120.500000
50% 10.540000 5930.500000 13.600000 43.600000 5135.000000
75% 12.647500 8187.250000 52.202500 59.275000 8312.500000
max 15.970000 25879.000000 97.510000 87.200000 9517.000000
# Constrói os gráficos de dispersão

plt.scatter(x=dados.education,y=dados.prestige)
plt.xlabel("anos de educação")
plt.ylabel("prestígio da ocupação")
plt.show()
../_images/ad83d2de57539bcb5f76ae001a4f9af2e3c0d4d666c4ad3089fcd3fa6b09e29e.png
import seaborn as sns


dados1 = dados[["education","income","women","prestige","type"]]

sns.pairplot(dados, kind='reg')
<seaborn.axisgrid.PairGrid at 0x1f5e9aa7670>
../_images/53f25256e4c5ee965ffa880169b8b6547bdecfc3fa3637b38ed6b6d75bac31c6.png
# Outra maneira, diferenciando por "type"

sns.pairplot(dados1, hue='type', kind='reg')
<seaborn.axisgrid.PairGrid at 0x1f58177a760>
../_images/59954498d85be284c3624d6dd55f68362c8db101e703491f4ec2a3d474a82be3.png
fig, ax = plt.subplots(figsize=(12,12))
sns.heatmap(dados1.corr(),annot=True, square=True, cmap="BuGn")

plt.title('Matriz de correlações de Pearson',fontsize=25)
Text(0.5, 1.0, 'Matriz de correlações de Pearson')
../_images/05cd3a951ed5c3e99d57e59650a0f905007f34c7c05e8e6c7ed97d1e36973938.png
from statsmodels.formula.api import ols

#Ajusta o modelo de regressão linear múltipla com Prestige como variável resposta
mod = ols('dados.prestige ~ dados.income + dados.education + dados.women',data=dados)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:         dados.prestige   R-squared:                       0.798
Model:                            OLS   Adj. R-squared:                  0.792
Method:                 Least Squares   F-statistic:                     129.2
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           6.26e-34
Time:                        01:55:40   Log-Likelihood:                -352.82
No. Observations:                 102   AIC:                             713.6
Df Residuals:                      98   BIC:                             724.1
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
===================================================================================
                      coef    std err          t      P>|t|      [0.025      0.975]
-----------------------------------------------------------------------------------
Intercept          -6.7943      3.239     -2.098      0.039     -13.222      -0.366
dados.income        0.0013      0.000      4.729      0.000       0.001       0.002
dados.education     4.1866      0.389     10.771      0.000       3.415       4.958
dados.women        -0.0089      0.030     -0.293      0.770      -0.069       0.051
==============================================================================
Omnibus:                        0.271   Durbin-Watson:                   1.687
Prob(Omnibus):                  0.873   Jarque-Bera (JB):                0.436
Skew:                          -0.085   Prob(JB):                        0.804
Kurtosis:                       2.729   Cond. No.                     3.35e+04
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 3.35e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
# Estimativa da variância do erro (sigma2), que é o MSE=SQE/(n-p)
res.mse_resid
61.56704276635243
# valores preditos de E(Y)
ypred=res.fittedvalues

# resíduo=observado-ajustado
residuo = res.resid

# objeto para a análise de pontos influentes
infl = res.get_influence()

# diagonal da matriz hat
hii = infl.hat_matrix_diag

# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal

# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external

# DFFITS
(dffits,p) = infl.dffits

# Distância de Cook
(cook,p) = infl.cooks_distance

Elabora os gráficos de todos os resíduos

fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')

for ax in fig.get_axes():
    ax.label_outer()
../_images/6ba79b15be2d9bee798f239a131d375ae80a6c099113695cd798fcf7ae2975fe.png
fig, (ax1, ax2, ax3) = plt.subplots(3)
ax1.scatter(dados.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
ax1.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(dados.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(dados.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')

for ax in fig.get_axes():
    ax.label_outer()
../_images/2c70b2b3b40d38c02f12936123cdb5722e1f6e66ecbc12ce42ce88c693c799bb.png

Identificando a observações que se destacam das demais em alguns gráficos de diagnóstico

# Instale a plotly se necessário
#!pip install plotly

import plotly.express as px

fig = px.scatter(x = dados.index, y=cook)
fig.show()

Identificadas as observações 1 e, em menor escala, a 52. Verifique o que elas tem de especial.

# x and y given as DataFrame columns
import plotly.express as px

fig = px.scatter(x = dados.index, y=hii)
fig.show()

Identificadas as observações 1 e 23. Verifique o que elas tem de especial.

# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
../_images/c238930598435fa856507137def09e09698524c93ab2db5f853d234476fcfc0f.png
# Os autores em https://rstudio-pubs-static.s3.amazonaws.com/63531_55f83f76f1104efd9b6d694f9228d55e.html
# sugerem a transformação logaritmica da variável income e verificamos se type deve ser adicionada

# Modelo de regressão com a variável "income" transformada

l_income = np.log2(dados.income) # base 2
le_income = np.log(dados.income) # base e

mod = ols('dados.prestige ~ l_income + dados.education + dados.women',data=dados)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:         dados.prestige   R-squared:                       0.835
Model:                            OLS   Adj. R-squared:                  0.830
Method:                 Least Squares   F-statistic:                     165.4
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           3.21e-38
Time:                        01:55:42   Log-Likelihood:                -342.51
No. Observations:                 102   AIC:                             693.0
Df Residuals:                      98   BIC:                             703.5
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
===================================================================================
                      coef    std err          t      P>|t|      [0.025      0.975]
-----------------------------------------------------------------------------------
Intercept        -110.9658     14.843     -7.476      0.000    -140.421     -81.511
l_income            9.3147      1.327      7.022      0.000       6.682      11.947
dados.education     3.7305      0.354     10.527      0.000       3.027       4.434
dados.women         0.0469      0.030      1.568      0.120      -0.012       0.106
==============================================================================
Omnibus:                        0.221   Durbin-Watson:                   1.828
Prob(Omnibus):                  0.895   Jarque-Bera (JB):                0.120
Skew:                           0.084   Prob(JB):                        0.942
Kurtosis:                       2.989   Cond. No.                         941.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Modelo de regressão com a variável "income" transformada e variável type

mod = ols('dados.prestige ~ l_income+dados.education+dados.women+dados.type',data=dados)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:         dados.prestige   R-squared:                       0.865
Model:                            OLS   Adj. R-squared:                  0.858
Method:                 Least Squares   F-statistic:                     118.3
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           1.71e-38
Time:                        01:55:42   Log-Likelihood:                -318.49
No. Observations:                  98   AIC:                             649.0
Df Residuals:                      92   BIC:                             664.5
Df Model:                           5                                         
Covariance Type:            nonrobust                                         
======================================================================================
                         coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------------
Intercept           -115.6722     18.802     -6.152      0.000    -153.014     -78.330
dados.type[T.prof]     5.2919      3.556      1.488      0.140      -1.770      12.354
dados.type[T.wc]      -3.2160      2.407     -1.336      0.185      -7.996       1.564
l_income              10.1582      1.602      6.340      0.000       6.976      13.340
dados.education        2.9738      0.602      4.940      0.000       1.778       4.170
dados.women            0.0838      0.032      2.601      0.011       0.020       0.148
==============================================================================
Omnibus:                        0.002   Durbin-Watson:                   1.953
Prob(Omnibus):                  0.999   Jarque-Bera (JB):                0.100
Skew:                          -0.006   Prob(JB):                        0.951
Kurtosis:                       2.844   Cond. No.                     1.28e+03
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.28e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
# Modelo de regressão final?
# Vamos exlcuir type com base no alto valor p, mas ela pode ser incluída em interação com income, veja a próxima célula
# Que outros critérios você utilizaria para a seleção de variáveis?


mod = ols('dados.prestige ~ l_income+dados.education',data=dados)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:         dados.prestige   R-squared:                       0.831
Model:                            OLS   Adj. R-squared:                  0.828
Method:                 Least Squares   F-statistic:                     243.3
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           6.11e-39
Time:                        01:55:42   Log-Likelihood:                -343.78
No. Observations:                 102   AIC:                             693.6
Df Residuals:                      99   BIC:                             701.4
Df Model:                           2                                         
Covariance Type:            nonrobust                                         
===================================================================================
                      coef    std err          t      P>|t|      [0.025      0.975]
-----------------------------------------------------------------------------------
Intercept         -95.1940     10.998     -8.656      0.000    -117.016     -73.372
l_income            7.9278      0.996      7.959      0.000       5.951       9.904
dados.education     4.0020      0.312     12.846      0.000       3.384       4.620
==============================================================================
Omnibus:                        0.092   Durbin-Watson:                   1.886
Prob(Omnibus):                  0.955   Jarque-Bera (JB):                0.121
Skew:                           0.067   Prob(JB):                        0.941
Kurtosis:                       2.897   Cond. No.                         260.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Análise extra: inclusão de interação de variáveis
# Modelo de regressão com interação 


mod = ols('dados.prestige ~ dados.type*(dados.income)',data=dados)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:         dados.prestige   R-squared:                       0.829
Model:                            OLS   Adj. R-squared:                  0.819
Method:                 Least Squares   F-statistic:                     88.94
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           1.08e-33
Time:                        01:55:42   Log-Likelihood:                -330.34
No. Observations:                  98   AIC:                             672.7
Df Residuals:                      92   BIC:                             688.2
Df Model:                           5                                         
Covariance Type:            nonrobust                                         
===================================================================================================
                                      coef    std err          t      P>|t|      [0.025      0.975]
---------------------------------------------------------------------------------------------------
Intercept                          13.9045      3.167      4.390      0.000       7.614      20.195
dados.type[T.prof]                 45.0190      4.291     10.492      0.000      36.497      53.541
dados.type[T.wc]                   18.9807      5.342      3.553      0.001       8.371      29.591
dados.income                        0.0040      0.001      7.276      0.000       0.003       0.005
dados.type[T.prof]:dados.income    -0.0032      0.001     -5.256      0.000      -0.004      -0.002
dados.type[T.wc]:dados.income      -0.0022      0.001     -2.238      0.028      -0.004      -0.000
==============================================================================
Omnibus:                        8.833   Durbin-Watson:                   1.932
Prob(Omnibus):                  0.012   Jarque-Bera (JB):                8.738
Skew:                           0.611   Prob(JB):                       0.0127
Kurtosis:                       3.805   Cond. No.                     8.76e+04
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 8.76e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
import itertools

##### Entendendo o que é a interação
# Veja que as retas de regressão são bastante diferentes para diferentes types

x = np.linspace(0,25000)
y1 = 13 + 0.004*x # blue collar
y2 =(13+18.98) + (0.004-0.0022)*x # white collar
y3 = (13+45) + (0.004-0.0032)*x    # prof

plt.plot(x, y1, label="blue collar", color="blue")
plt.plot(x, y2, label="white collar", color="gray")
plt.plot(x, y3, label="prof",color="red")
colors = itertools.cycle(["b", "r", "gray"])
groups = dados.groupby("type")
for name, group in groups:
    plt.plot(group["income"], group["prestige"], marker="o", linestyle="", label=name, color=next(colors))
plt.legend()
plt.xlabel("income")
plt.ylabel("prestige")
plt.show()
../_images/84aa563e353c6889197dd964904ce3b3b5f1ea844711723727f1f1db16f9e9f2.png

Prática 1#

Modelos Lineares#

Exercício: dados Prestige#

Fontes:

O arquivo Prestige.csv contém 102 observações e 7 variáveis. A descrição das variáveis no conjunto de dados é a seguinte:

  • occupation: profissão

  • education: número médio de anos de estudo dos titulares da profissão.

  • income: A renda média dos ocupantes ocupacionais, em dólares.

  • women: a porcentagem de mulheres na ocupação.

  • prestige: a classificação média de prestígio para a ocupação.

  • census: o código da ocupação utilizado na pesquisa.

  • type: profissional e gerencial (prof), colarinho branco (wc), colarinho azul (bc) ou ausente (NA) (Fox e Weisberg 2011)

Ajuste um modelo de regressão linear múltiplo para compreender a associação entre o prestígio e as preditoras renda, educação e mulheres. Verifique se o modelo é adequado.

import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
import pandas as pd
from scipy import stats

# Dados Prestige - Leitura dos dados a partir de uma pasta local
#pkgdir = '/hdd/MBA/ECD/Data'
#dados = pd.read_csv(f'{pkgdir}/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')
dados = pd.read_csv('Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')
# Leitura dos dados direto do github
#dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/Prestige.csv', sep = ',', na_values = '-', encoding= 'unicode_escape')

dados.head()
occupation education income women prestige census type
0 gov.administrators 13.11 12351 11.16 68.8 1113 prof
1 general.managers 12.26 25879 4.02 69.1 1130 prof
2 accountants 12.77 9271 15.70 63.4 1171 prof
3 purchasing.officers 11.42 8865 9.11 56.8 1175 prof
4 chemists 14.62 8403 11.68 73.5 2111 prof
dados.head(20)
occupation education income women prestige census type
0 gov.administrators 13.11 12351 11.16 68.8 1113 prof
1 general.managers 12.26 25879 4.02 69.1 1130 prof
2 accountants 12.77 9271 15.70 63.4 1171 prof
3 purchasing.officers 11.42 8865 9.11 56.8 1175 prof
4 chemists 14.62 8403 11.68 73.5 2111 prof
5 physicists 15.64 11030 5.13 77.6 2113 prof
6 biologists 15.09 8258 25.65 72.6 2133 prof
7 architects 15.44 14163 2.69 78.1 2141 prof
8 civil.engineers 14.52 11377 1.03 73.1 2143 prof
9 mining.engineers 14.64 11023 0.94 68.8 2153 prof
10 surveyors 12.39 5902 1.91 62.0 2161 prof
11 draughtsmen 12.30 7059 7.83 60.0 2163 prof
12 computer.programers 13.83 8425 15.33 53.8 2183 prof
13 economists 14.44 8049 57.31 62.2 2311 prof
14 psychologists 14.36 7405 48.28 74.9 2315 prof
15 social.workers 14.21 6336 54.77 55.1 2331 prof
16 lawyers 15.77 19263 5.13 82.3 2343 prof
17 librarians 14.15 6112 77.10 58.1 2351 prof
18 vocational.counsellors 15.22 9593 34.89 58.3 2391 prof
19 ministers 14.50 4686 4.14 72.8 2511 prof
dados.isnull().sum()
occupation    0
education     0
income        0
women         0
prestige      0
census        0
type          4
dtype: int64
dados['type'].fillna(dados['type'].mode()[0],inplace=True)
dados.drop(['occupation','census'],axis=1,inplace=True)
dados.head()
education income women prestige type
0 13.11 12351 11.16 68.8 prof
1 12.26 25879 4.02 69.1 prof
2 12.77 9271 15.70 63.4 prof
3 11.42 8865 9.11 56.8 prof
4 14.62 8403 11.68 73.5 prof

Divisão em treinamento e teste#

from sklearn.model_selection import train_test_split

train, test = train_test_split(dados, test_size=0.2, random_state=0, stratify=dados[['type']])
train.describe()
education income women prestige
count 81.000000 81.000000 81.000000 81.000000
mean 10.774321 6928.320988 27.667160 47.171605
std 2.741272 3989.932120 30.426826 16.696850
min 6.380000 918.000000 0.000000 14.800000
25% 8.490000 4330.000000 3.160000 35.700000
50% 10.640000 6259.000000 13.620000 43.700000
75% 12.770000 8206.000000 48.280000 59.600000
max 15.940000 25879.000000 97.510000 82.300000
train.prestige.hist()
<AxesSubplot:>
../_images/f224948cbb9bcd6bd44dbbd736c125fb569c4f0664709fe11eaf149d0503d09b.png
# Constrói os gráficos de dispersão

plt.scatter(x=train.education,y=train.prestige)
plt.xlabel("anos de educação")
plt.ylabel("prestígio da ocupação")
plt.show()
../_images/a3afa99d1ecbfacda59e0b7d63498f11de14cc477f5482b3562e8a1e96c5f373.png
import seaborn as sns

sns.pairplot(train, kind='reg')
plt.show()
../_images/3fbcc0503ee3da591123bc56678dd0396cb4bb8fd05ec33dd94d02f91b6e913c.png
# Outra maneira, diferenciando por "type"

sns.pairplot(train, hue='type', kind='reg')
plt.show()
../_images/6233090b120aba5a9dfcff5732be00c92962c13d28f334ade5a5daedbce16ae5.png
fig, ax = plt.subplots(figsize=(12,12))
sns.heatmap(train.drop(['type'],axis=1).corr(),annot=True, square=True, cmap="BuGn")

plt.title('Matriz de correlações de Pearson',fontsize=20)
plt.show()
../_images/c824a88e8b0bb9bd3ee179801359116cba7080def8c22ef7f93615b4e724e4dc.png
from statsmodels.formula.api import ols

#Ajusta o modelo de regressão linear múltipla com Prestige como variável resposta
mod = ols('prestige ~ income + education + women',data=train)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               prestige   R-squared:                       0.783
Model:                            OLS   Adj. R-squared:                  0.775
Method:                 Least Squares   F-statistic:                     92.73
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           1.72e-25
Time:                        02:01:13   Log-Likelihood:                -280.54
No. Observations:                  81   AIC:                             569.1
Df Residuals:                      77   BIC:                             578.7
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept     -5.9608      3.633     -1.641      0.105     -13.195       1.273
income         0.0013      0.000      3.843      0.000       0.001       0.002
education      4.1283      0.433      9.535      0.000       3.266       4.990
women         -0.0076      0.037     -0.208      0.836      -0.080       0.065
==============================================================================
Omnibus:                        0.354   Durbin-Watson:                   2.114
Prob(Omnibus):                  0.838   Jarque-Bera (JB):                0.378
Skew:                          -0.151   Prob(JB):                        0.828
Kurtosis:                       2.855   Cond. No.                     3.31e+04
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 3.31e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
mod = ols('prestige ~ income + education',data=train)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               prestige   R-squared:                       0.783
Model:                            OLS   Adj. R-squared:                  0.778
Method:                 Least Squares   F-statistic:                     140.8
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           1.30e-26
Time:                        02:01:13   Log-Likelihood:                -280.57
No. Observations:                  81   AIC:                             567.1
Df Residuals:                      78   BIC:                             574.3
Df Model:                           2                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept     -6.0249      3.598     -1.675      0.098     -13.187       1.137
income         0.0013      0.000      5.000      0.000       0.001       0.002
education      4.0879      0.385     10.630      0.000       3.322       4.853
==============================================================================
Omnibus:                        0.342   Durbin-Watson:                   2.111
Prob(Omnibus):                  0.843   Jarque-Bera (JB):                0.412
Skew:                          -0.148   Prob(JB):                        0.814
Kurtosis:                       2.813   Cond. No.                     3.30e+04
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 3.3e+04. This might indicate that there are
strong multicollinearity or other numerical problems.

Análise de resíduos#

# Estimativa da variância do erro (sigma2), que é o MSE=SQE/(n-p)
res.mse_resid
62.01986979474898
# valores preditos de E(Y)
ypred=res.fittedvalues

# resíduo=observado-ajustado
residuo = res.resid

# objeto para a análise de pontos influentes
infl = res.get_influence()

# diagonal da matriz hat
hii = infl.hat_matrix_diag

# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal

# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external

# DFFITS
(dffits,p) = infl.dffits

# Distância de Cook
(cook,p) = infl.cooks_distance

Elabora os gráficos de todos os resíduos

fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')

for ax in fig.get_axes():
    ax.label_outer()
../_images/eaf2caf4e55b57dc95fc3d3275b7ae1dea6686774e5753213357878750ac502b.png
fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(train.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
ax1.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(train.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(train.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')

for ax in fig.get_axes():
    ax.label_outer()
../_images/843da4a80dfc0c4237ebc3b4afcf47d87e1bbf0ea1a758c647d1824dadc4e3ab.png

Identificando a observações que se destacam das demais em alguns gráficos de diagnóstico

# Instale a plotly se necessário
#!pip install plotly

import plotly.express as px

fig = px.scatter(x = train.index, y=cook)
fig.show()
import plotly.express as px

fig = px.scatter(x = train.index, y=residuo)
fig.show()
fig = px.scatter(x = train.index, y=ypred)
fig.show()
# x and y given as DataFrame columns
import plotly.express as px

fig = px.scatter(x = train.index, y=hii)
fig.show()

Identificada a observação 1. Verifique o que elas tem de especial.

train.iloc[1,]
education    12.27
income       14032
women         0.58
prestige      66.1
type          prof
Name: 95, dtype: object
train.describe()
education income women prestige
count 81.000000 81.000000 81.000000 81.000000
mean 10.774321 6928.320988 27.667160 47.171605
std 2.741272 3989.932120 30.426826 16.696850
min 6.380000 918.000000 0.000000 14.800000
25% 8.490000 4330.000000 3.160000 35.700000
50% 10.640000 6259.000000 13.620000 43.700000
75% 12.770000 8206.000000 48.280000 59.600000
max 15.940000 25879.000000 97.510000 82.300000
# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
../_images/33c4962c32e1c7a7ebf5d1eaa311929bc85d29c512cb67b93da5742ff4893469.png
from scipy import stats
stats.shapiro(residuo)
ShapiroResult(statistic=0.9904203414916992, pvalue=0.8151065707206726)

Teste do modelo#

y_pred = res.predict(test)
plt.figure(figsize=(6,6))
plt.scatter(test.prestige,y_pred)
xi = np.min([test.prestige,y_pred])
xf = np.max([test.prestige,y_pred])
xlim = [xi-5,xf+5]
plt.xlim(xlim)
plt.ylim(xlim)
abline_values = [1 * i + 0 for i in xlim]
plt.plot(xlim, abline_values, 'gray', ls='--')
plt.xlabel('Income')
plt.ylabel('Prediction')
plt.show()
../_images/cc43d029a2e9faab7352bdde11602223e69272945c9d3445c6fe818eb6fce39a.png
from sklearn.metrics import r2_score, mean_squared_error

rmse = mean_squared_error(test.prestige, y_pred,squared=False)
r2 = r2_score(test.prestige,y_pred)

print('r2 = ', np.round(r2,2))
print('RMSE = ', np.round(rmse,2))
r2 =  0.84
RMSE =  7.59

Transformação da variável ‘income’#

# Os autores em https://rstudio-pubs-static.s3.amazonaws.com/63531_55f83f76f1104efd9b6d694f9228d55e.html
# sugerem a transformação logaritmica da variável income e verificamos se type deve ser adicionada

# Modelo de regressão com a variável "income" transformada

train_l_income = np.log(train.income) # base e
test_l_income = np.log(test.income)

train_l = train.copy()
test_l = test.copy()
train_l['l_income'] = train_l_income
test_l['l_income'] = test_l_income

mod = ols('prestige ~ l_income + education + women',data=train_l)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               prestige   R-squared:                       0.822
Model:                            OLS   Adj. R-squared:                  0.815
Method:                 Least Squares   F-statistic:                     118.5
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           8.89e-29
Time:                        02:01:16   Log-Likelihood:                -272.56
No. Observations:                  81   AIC:                             553.1
Df Residuals:                      77   BIC:                             562.7
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept   -113.0827     18.352     -6.162      0.000    -149.627     -76.539
l_income      13.7891      2.339      5.896      0.000       9.132      18.446
education      3.6299      0.400      9.073      0.000       2.833       4.427
women          0.0407      0.035      1.170      0.245      -0.029       0.110
==============================================================================
Omnibus:                        1.053   Durbin-Watson:                   1.971
Prob(Omnibus):                  0.591   Jarque-Bera (JB):                0.592
Skew:                           0.182   Prob(JB):                        0.744
Kurtosis:                       3.207   Cond. No.                         976.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# Modelo de regressão com a variável "income" transformada e variável type

mod = ols('prestige ~ l_income+education+women+type',data=train_l)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               prestige   R-squared:                       0.856
Model:                            OLS   Adj. R-squared:                  0.847
Method:                 Least Squares   F-statistic:                     89.47
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           3.57e-30
Time:                        02:01:16   Log-Likelihood:                -263.86
No. Observations:                  81   AIC:                             539.7
Df Residuals:                      75   BIC:                             554.1
Df Model:                           5                                         
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          t      P>|t|      [0.025      0.975]
--------------------------------------------------------------------------------
Intercept      -95.6422     18.509     -5.167      0.000    -132.514     -58.771
type[T.prof]     6.3789      4.092      1.559      0.123      -1.772      14.530
type[T.wc]      -4.3895      2.731     -1.607      0.112      -9.830       1.051
l_income        12.5355      2.173      5.770      0.000       8.208      16.863
education        2.8905      0.646      4.476      0.000       1.604       4.177
women            0.0568      0.034      1.650      0.103      -0.012       0.125
==============================================================================
Omnibus:                        0.069   Durbin-Watson:                   2.021
Prob(Omnibus):                  0.966   Jarque-Bera (JB):                0.208
Skew:                           0.056   Prob(JB):                        0.901
Kurtosis:                       2.779   Cond. No.                     1.09e+03
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 1.09e+03. This might indicate that there are
strong multicollinearity or other numerical problems.

Modelo de regressão final?

Vamos exlcuir type com base no alto valor p, mas ela pode ser incluída em interação com income, veja a próxima célula

Que outros critérios você utilizaria para a seleção de variáveis?

mod = ols('prestige ~ l_income+education',data=train_l)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               prestige   R-squared:                       0.819
Model:                            OLS   Adj. R-squared:                  0.814
Method:                 Least Squares   F-statistic:                     176.3
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           1.16e-29
Time:                        02:01:16   Log-Likelihood:                -273.28
No. Observations:                  81   AIC:                             552.6
Df Residuals:                      78   BIC:                             559.7
Df Model:                           2                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept    -98.9631     13.862     -7.139      0.000    -126.561     -71.365
l_income      12.0142      1.785      6.732      0.000       8.461      15.567
education      3.8578      0.350     11.011      0.000       3.160       4.555
==============================================================================
Omnibus:                        0.879   Durbin-Watson:                   2.008
Prob(Omnibus):                  0.644   Jarque-Bera (JB):                0.529
Skew:                           0.190   Prob(JB):                        0.768
Kurtosis:                       3.114   Cond. No.                         246.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Análise extra: inclusão de interação de variáveis

Modelo de regressão com interação

\(prestige = \beta_0 + \beta_1*l\_income + \beta_2*education + \beta_3*type + \beta_4*type*l\_income + \beta_5*type*education\)

mod = ols('prestige ~ type*(l_income)+type*(education)',data=train_l)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               prestige   R-squared:                       0.865
Model:                            OLS   Adj. R-squared:                  0.850
Method:                 Least Squares   F-statistic:                     57.61
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           3.03e-28
Time:                        02:01:16   Log-Likelihood:                -261.40
No. Observations:                  81   AIC:                             540.8
Df Residuals:                      72   BIC:                             562.3
Df Model:                           8                                         
Covariance Type:            nonrobust                                         
==========================================================================================
                             coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------------------
Intercept               -107.2086     19.057     -5.626      0.000    -145.198     -69.219
type[T.prof]              92.1316     34.235      2.691      0.009      23.885     160.378
type[T.wc]                30.5116     39.957      0.764      0.448     -49.141     110.164
l_income                  14.4362      2.406      6.001      0.000       9.640      19.232
type[T.prof]:l_income     -9.4205      3.835     -2.456      0.016     -17.066      -1.775
type[T.wc]:l_income       -6.2796      4.533     -1.385      0.170     -15.317       2.758
education                  2.4575      0.874      2.811      0.006       0.715       4.200
type[T.prof]:education     0.1608      1.399      0.115      0.909      -2.627       2.949
type[T.wc]:education       1.9388      2.105      0.921      0.360      -2.258       6.136
==============================================================================
Omnibus:                        0.165   Durbin-Watson:                   1.979
Prob(Omnibus):                  0.921   Jarque-Bera (JB):                0.003
Skew:                          -0.005   Prob(JB):                        0.998
Kurtosis:                       3.028   Cond. No.                         986.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
test_l
education income women prestige type l_income
42 9.22 5511 7.62 36.1 wc 8.614501
3 11.42 8865 9.11 56.8 prof 9.089866
72 7.42 1890 72.24 23.2 bc 7.544332
20 15.97 12480 19.59 84.6 prof 9.431883
44 10.51 3161 96.14 38.1 wc 8.058644
74 6.74 3485 39.48 28.8 bc 8.156223
30 12.79 5180 76.04 67.5 wc 8.552560
18 15.22 9593 34.89 58.3 prof 9.168789
23 15.96 25308 10.56 87.2 prof 10.138876
35 11.49 3148 95.97 41.9 wc 8.054523
53 9.93 2370 3.69 23.3 bc 7.770645
92 7.81 4549 2.46 29.9 bc 8.422663
99 8.37 4753 0.00 26.1 bc 8.466531
98 7.93 4224 3.59 25.1 bc 8.348538
10 12.39 5902 1.91 62.0 prof 8.683047
11 12.30 7059 7.83 60.0 prof 8.862059
62 9.46 611 96.53 25.9 bc 6.415097
76 8.81 6686 4.28 44.2 bc 8.807771
27 9.45 3485 76.14 34.9 bc 8.156223
89 8.24 8880 0.65 51.1 bc 9.091557
46 11.13 5052 56.10 51.1 wc 8.527539

Teste do modelo#

y_pred = res.predict(test_l)
plt.figure(figsize=(6,6))
plt.scatter(test_l.prestige,y_pred)
xi = np.min([test_l.prestige,y_pred])
xf = np.max([test_l.prestige,y_pred])
xlim = [xi-5,xf+5]
plt.xlim(xlim)
plt.ylim(xlim)
abline_values = [1 * i + 0 for i in xlim]
plt.plot(xlim, abline_values, 'gray', ls='--')
plt.xlabel('Income')
plt.ylabel('Prediction')
plt.show()
../_images/65ed5533b395b8f9c736dee2c9239f587335362b00b3a2e77a80eff81c488303.png
from sklearn.metrics import r2_score, mean_squared_error

rmse = mean_squared_error(test.prestige, y_pred,squared=False)
r2 = r2_score(test.prestige,y_pred)

print('r2 = ', np.round(r2,2))
print('RMSE = ', np.round(rmse,2))
r2 =  0.82
RMSE =  8.1

Seleção de atributos com stepwise#

Transformação logaritimica do income

Transformando a variável type em valores numéricos (get_dummy)

train_l = pd.get_dummies(train_l, columns=['type'])
test_l = pd.get_dummies(test_l, columns=['type'])
train_l = train_l.astype({'type_bc': int, 'type_wc': int, 'type_prof': int})
test_l = test_l.astype({'type_bc': int, 'type_wc': int, 'type_prof': int})
train_l.head()
education income women prestige l_income type_bc type_prof type_wc
14 14.36 7405 48.28 74.9 8.909911 0 1 0
95 12.27 14032 0.58 66.1 9.549096 0 1 0
52 9.62 918 7.00 14.8 6.822197 1 0 0
93 8.33 6928 0.61 42.9 8.843326 1 0 0
49 9.84 7482 17.04 41.5 8.920255 0 0 1
import statsmodels.api as sm

def backward_elimination(data, target, significance_level = 0.05):
    features = data.columns.tolist()
    while(len(features)>0):
        features_with_constant = sm.add_constant(data[features])
        p_values = sm.OLS(target, features_with_constant).fit().pvalues[1:]  # we exclude the intercept
        max_p_value = p_values.max()
        if max_p_value >= significance_level:
            excluded_feature = p_values.idxmax()
            features.remove(excluded_feature)
        else:
            break 
    return features

# Use the function on your data
selected_features = backward_elimination(train_l[['education', 'l_income', 'women','type_bc','type_prof','type_wc']],
                                         train_l['prestige'],significance_level = 0.02)
print('The selected features are:', selected_features)
The selected features are: ['education', 'l_income', 'type_wc']
#mod = ols('prestige ~ education + l_income + women + type_bc + type_prof + type_prof + type_wc',data=train_l)
mod = ols('prestige ~ education + l_income + type_wc',data=train_l)
res = mod.fit()
print(res.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:               prestige   R-squared:                       0.842
Model:                            OLS   Adj. R-squared:                  0.836
Method:                 Least Squares   F-statistic:                     136.9
Date:                Wed, 17 Jan 2024   Prob (F-statistic):           8.85e-31
Time:                        02:01:17   Log-Likelihood:                -267.70
No. Observations:                  81   AIC:                             543.4
Df Residuals:                      77   BIC:                             553.0
Df Model:                           3                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept    -84.8363     13.682     -6.201      0.000    -112.080     -57.592
education      4.0923      0.336     12.164      0.000       3.422       4.762
l_income      10.2639      1.755      5.847      0.000       6.769      13.759
type_wc       -6.3851      1.894     -3.371      0.001     -10.157      -2.613
==============================================================================
Omnibus:                        0.244   Durbin-Watson:                   2.073
Prob(Omnibus):                  0.885   Jarque-Bera (JB):                0.405
Skew:                           0.101   Prob(JB):                        0.817
Kurtosis:                       2.719   Cond. No.                         259.
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Teste do modelo#

y_pred = res.predict(test_l)
plt.figure(figsize=(6,6))
plt.scatter(test_l.prestige,y_pred)
xi = np.min([test_l.prestige,y_pred])
xf = np.max([test_l.prestige,y_pred])
xlim = [xi-5,xf+5]
plt.xlim(xlim)
plt.ylim(xlim)
abline_values = [1 * i + 0 for i in xlim]
plt.plot(xlim, abline_values, 'gray', ls='--')
plt.xlabel('Income')
plt.ylabel('Prediction')
plt.show()
../_images/4807a9775c34bd2cdc7e39ad7f4ee237cce4eccd7f3cf5b1c10fa5e55083e0ae.png
from sklearn.metrics import r2_score, mean_squared_error

rmse = mean_squared_error(test_l.prestige, y_pred,squared=False)
r2 = r2_score(test.prestige,y_pred)

print('r2 = ', np.round(r2,2))
print('RMSE = ', np.round(rmse,2))
r2 =  0.84
RMSE =  7.65

Análise de resíduos#

# Estimativa da variância do erro (sigma2), que é o MSE=SQE/(n-p)
res.mse_resid
45.72493077668226
# valores preditos de E(Y)
ypred=res.fittedvalues

# resíduo=observado-ajustado
residuo = res.resid

# objeto para a análise de pontos influentes
infl = res.get_influence()

# diagonal da matriz hat
hii = infl.hat_matrix_diag

# resíduo studentizado (internamente)
res_stud = infl.resid_studentized_internal

# resíduo studentizado com i-ésima observação deletada (externamente)
res_stud_del = infl.resid_studentized_external

# DFFITS
(dffits,p) = infl.dffits

# Distância de Cook
(cook,p) = infl.cooks_distance

Elabora os gráficos de todos os resíduos

fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(ypred, residuo)
ax1.set_ylabel('$y-\hat{y}$')
ax1.set_title('Resíduos')
ax1.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax2.scatter(ypred, res_stud)
ax2.set_ylabel('studentizado')
ax2.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.scatter(ypred, res_stud_del)
ax3.set_ylabel('studentizado -(i)')
ax3.hlines(0,xmin=min(ypred),xmax=max(ypred),color='gray')
ax3.set_xlabel('$\hat{Y}$')

for ax in fig.get_axes():
    ax.label_outer()
../_images/28d8851b8a3b617a920332ee8b31b1081209d3886b9e39668c20fd45ea485fdd.png
fig, (ax1, ax2, ax3) = plt.subplots(3,figsize=(8,8))
ax1.scatter(train.index, dffits)
ax1.set_ylabel('DFFITS')
ax1.set_title('Detecção de pontos influentes')
ax1.hlines(0,xmin=1,xmax=102,color='gray')
ax2.scatter(train.index, cook)
ax2.set_ylabel('distância de Cook')
ax3.scatter(train.index, hii)
ax3.set_ylabel('$h_{ii}$')
ax3.set_xlabel('índice')

for ax in fig.get_axes():
    ax.label_outer()
../_images/a16fae2f0bb6812ebad8d96ea4da474804b06a6116331b40abc37593693e4867.png

Identificando a observações que se destacam das demais em alguns gráficos de diagnóstico

# Instale a plotly se necessário
#!pip install plotly

import plotly.express as px

fig = px.scatter(x = train.index, y=cook)
fig.show()
# x and y given as DataFrame columns
import plotly.express as px

fig = px.scatter(x = train.index, y=hii)
fig.show()

Identificada a observação 1. Verifique o que elas tem de especial.

train.iloc[52,]
education    11.09
income        6992
women        24.44
prestige      47.1
type            wc
Name: 55, dtype: object
train.describe()
education income women prestige
count 81.000000 81.000000 81.000000 81.000000
mean 10.774321 6928.320988 27.667160 47.171605
std 2.741272 3989.932120 30.426826 16.696850
min 6.380000 918.000000 0.000000 14.800000
25% 8.490000 4330.000000 3.160000 35.700000
50% 10.640000 6259.000000 13.620000 43.700000
75% 12.770000 8206.000000 48.280000 59.600000
max 15.940000 25879.000000 97.510000 82.300000
# Verificando a suposição de distribuição Normal dos resíduos
stats.probplot(residuo, plot=plt)
plt.xlabel('quantis teóricos')
plt.ylabel('resíduos ordenados')
plt.show()
../_images/cbb23f93a37b17bf4c2db164bba823a738b70ce9ba10b136a1c4b7b68cd8c460.png
from scipy import stats
stats.shapiro(residuo)
ShapiroResult(statistic=0.9898176789283752, pvalue=0.7772987484931946)

Aula 8 - Modelos Lineares Generalizados#

Programa#

  • Modelos lineares generalizados.

  • Família exponencial de distribuições.

  • O modelo de regressão logística

  • A qualidade do ajuste.

  • Aplicações (enfoque frequentista e enfoque Bayesiano)

Referências e Leituras sugeridas:

Modelos lineares#

Objetivos

Predizer \(Y\) a partir do conhecimento de variáveis preditoras em \(X = x\).

Um modelo linear considerando pares observados de \((X_i, Y_i), i=1,\ldots,n\), em que \(X_i\) é um vetor de preditoras é dado por

\[\large{Y_i = X_i^\top\beta+\epsilon_i,}\]

em que, para o \(i\)-ésimo elemento amostral, temos

  • \(Y_i\) é a variável resposta (aleatória observável),

  • \(X_i\) contém variáveis preditoras (vetor conhecido, ou seja, não-aleatório),

  • \(\beta\) é um vetor de parâmetros de interesse, que queremos estimar,

  • \(\epsilon_i\) é o erro aleatório (não observável).

Suposições do modelo linear geral:

  • \( E(Y_i) = \mu_i = X_i^\top\beta\)

  • \(\epsilon_i \stackrel{i.i.d}{\sim} N(0, \sigma^2)\)

  • Consequentemente \( Y_i|X_i \sim N(\mu_i, \sigma^2).\)

O modelo linear (normal) é muito útil, mas nem sempre as suposições estão satisfeitas. É importante ter metodologias mais adequadas, em especial, para casos em que

  • A variável resposta não possui distribuição Normal.

  • A associação entre a resposta e as preditoras não é linear.

Modelo linear generalizado (MLG)#

Nelder, John; Wedderburn, Robert (1972). Generalized Linear Models. Blackwell Publishing. Journal of the Royal Statistical Society. Series A (General). 135: 370–384. JSTOR 2344614. doi:10.2307/2344614

Os MLGs propõem a modelagem para variáveis na família exponencial de distribuições, que inclui

  • Binomial (Bernoulli)

  • Poisson

  • Normal

  • Binomial Negativa

  • Gama

  • Gaussiana Inversa

  • outras

A família exponencial de distribuições#

Seja \(Y_i\) uma variável aleatória cuja distribuição pode ser escrita na forma

\[\large f(y,\theta_i, \phi) = exp[\phi\{y \theta_i - b(\theta_i)\} + c(y, \phi)]\]

Dizemos que a distribuição de \(Y_i\) pertence à família exponencial e escrevemos $\(\large Y_i \sim FE(\mu_i, \phi),\)$

em que \(\mu_i = E(Y_i) = b'(\theta_i)\) é o parâmetro de posição e \(\phi^{-1}\) o parâmetro de dispersão.

Além disso, \(Var(Y_i) = \phi^{-1}V_i\) com \(V_i = \displaystyle\frac{d\mu}{d\theta}\) a função de variância.

Então dizemos que um MLG é determinado pela função de ligação \(g(\mu_i) = \eta_i\) e pela função de variância \(V(\mu)\).

Valores de \(b\), \(\theta\), \(\phi\) e \(V(\mu)\) para algumas distribuições são apresentados na tabela a seguir

Distribuição $b$
$\theta$
$\phi$
$V(\mu)$
Normal \begin{eqnarray}\theta^2/2\end{eqnarray} \begin{eqnarray}\mu\end{eqnarray} \begin{eqnarray}\sigma^{-2}\end{eqnarray} \begin{eqnarray}1\end{eqnarray}
Poisson \begin{eqnarray}e^{\theta}\end{eqnarray} \begin{eqnarray}\log \mu\end{eqnarray} \begin{eqnarray}1\end{eqnarray} \begin{eqnarray}\mu\end{eqnarray}
Binomial \begin{eqnarray}\log\left(1+e^\theta\right)\end{eqnarray} \begin{eqnarray} \log\{\mu/(1-\mu)\}\end{eqnarray} \begin{eqnarray} n\end{eqnarray} \begin{eqnarray} \mu(1-\mu)\end{eqnarray}

Função de ligação#

O modelo linear generalizado é definido por

\[\begin{split}\begin{array}{c}\large Y_i \sim FE(\mu_i, \phi) \\\large g(\mu_i) = \eta_i\end{array}\end{split}\]

em que

  • \( \eta_i = X_i^\top\beta\) é o preditor linear,

  • \(\beta = (\beta_0, \beta_1,\ldots, \beta_p)^\top\), \(p<n\) é um vetor de parâmetros desconhecidos (coeficientes da regressão),

  • \( X_i = (1, X_{i1},\ldots, X_{ip})^\top\) representa os valores de \(p\) variáveis preditoras e

  • \(g(\mu_i)\) é a função de ligação, uma função monótona e diferenciável.

Exemplos#

Modelo Normal#

Se \(Y\sim N(\mu, \sigma^2)\), com densidade

\[\begin{split}\begin{array}{lll}f(y) &=& \displaystyle\frac{1}{\sqrt{2\pi\sigma^2}}\exp\left\{-\frac{1}{2\sigma^2}(y-\mu)^2\right\} = \\ &=&\displaystyle\exp\left[ \left\{ \frac{1}{\sigma^2}\left( \mu y - \frac{\mu^2}{2}\right) \right\}- \frac{1}{2} \left\{ \log 2 \pi \sigma^2 + \frac{y^2}{\sigma^2}\right\}\right],\end{array}\end{split}\]

em que \(-\infty < \mu < \infty\) e \(\sigma^2>0\).

Logo, \(\theta = \mu\), \(b(\theta) = \theta^2/2 \), \(\phi=\sigma^{-2}\) e \(c(y, \phi) = \frac{1}{2} \log(\phi/2\pi) - \phi y^2/2\). Verifica-se que \(V(\mu) = 1\).

Modelo Poisson: para dados de contagem#

Se \(Y\sim P(\mu)\), com densidade dada por

\[ f(y) =P(Y=y) = \displaystyle\frac{e^{-\mu} \mu^y}{y!} = exp(y \log\mu -\mu -\log y!), \]

em que \(\mu>0\) e \(y=0,1,...\)

Assim \(\theta = \log\mu\), \(b(\theta) = e^\theta\), \(\phi=1\) e \(c(y, \phi) = -\log y!\). Segue que \(V(\mu) = \mu\).

Modelo Binomial: para modelar proporções#

Se \(Y^\star\) é a proporção de sucessos em \(n\) ensaios independentes de Bernoulli, cada um com probabilidade de sucesso \(\mu\), então \(nY^\star\sim B(n,\mu)\), com densidade dada por

\[\begin{split}\begin{array}{lll}f(y) &=& P(Y=y) = \left(\begin{array}{c} n\\ ny^\star\end{array}\right)\mu ^{ny^\star}(1-\mu)^{n-ny^\star} = \\ &=&\exp\left\{\log \left(\begin{array}{c} n\\ ny^\star\end{array}\right) + ny^\star \log\left(\displaystyle\frac{\mu}{1-\mu} + n\log(1-\mu)\right)\right\},\end{array} \end{split}\]

em que \(\mu>0\), \(y^\star<1\). Nesse caso, \(\phi =n\), \(\theta = \log\displaystyle\left(\frac{\mu}{1-\mu}\right)\), \(b(\theta) = \log(1+e^\theta)\), e \(c(y^\star, \phi) = \log \left(\begin{array}{c} \phi\\ \phi y^\star\end{array}\right)\). Segue que \(V(\mu) = \mu(1-\mu)\).

Função de ligação canônica#

Se \(\theta_i = \eta_i = X_i^\top\beta\), então \(\eta\) é chamada de ligação canônica.

  • No modelo normal, a ligação canônica é \(\eta = \mu \)

  • No modelo Poisson, a ligação canônica é \(\eta = \log\mu\)

  • No modelo binomial, a ligação canônica é \(\eta = \log\displaystyle\left\{\frac{\mu}{1-\mu}\right\}\)

O modelo de regressão logística#

Um dos modelos lineares mais utilizados é o modelo de regressão logística.

Neste modelo, consideramos

\(Y_i = \left\{\begin{array}{l}1, \mbox{ se o indivíduo i possui determinada característica}\\ 0, \mbox{ caso contrário}\end{array}\right.\)

Supondo que \(P(Y_i|X_i) = \pi(X_i)\) e que

\[\log\left\{\displaystyle\frac{\pi(x)}{1-\pi(x)}\right\} = \alpha+\beta x\]

e queremos estimar os parâmetros \(\alpha\) e \(\beta\) para compreender como \(X_i\) e \(Y_i\) estão associados.

Suponha que \(X_i=1\) indique que o indivíduo \(i\) possui um fator de risco para uma determinada doença (indicada por \(Y_i=1)\) e foram observados \(n_1\) indivíduos com a presença deste fator, e \(n_2\) indivíduos sem a presença deste fator (\(X_i=0\)).

Para os indivíduos que possuiam o fator, a chance de desenvolvimento da doença fica

\[\displaystyle\frac{\pi(1)}{1-\pi(1)} = e^{\alpha + \beta},\]

enquanto que a chance de desenvolvimento da doença no indivíduo com ausência do fator é

\[\displaystyle\frac{\pi(0)}{1-\pi(0)} = e^{\alpha }.\]

A razão de chances nesse caso fica

\[\psi = \displaystyle\frac{\pi(1)/(1-\pi(1))}{\pi(0)/(1-\pi(0))} = e^\beta.\]

Regressão logística múltipla#

O modelo de regressão logística pode ser estendido para incluir \(p\) variáveis preditoras:

\(Y_i = \left\{\begin{array}{l}1, \mbox{ se o indivíduo i possui determinada característica}\\ 0, \mbox{ caso contrário}\end{array}\right.\)

Supondo que \(P(Y_i|X_i) = \pi(X_i)\) e que

\[\log\left\{\displaystyle\frac{\pi(x)}{1-\pi(x)}\right\} = \beta_0 + \beta_1 x_{1} + \beta_2 x_{2} + \ldots + \beta_p x_{p}.\]

A estimação dos parâmetros#

No enfoque clássico, é comum obter os estimadores de máxima verossimilhança por métodos iterativos como

  • Método de Newton-Raphson

  • Método escore de Fisher

  • Método dos mínimos quadrados ou mínimos quadrados reponderados

No enfoque Bayesiano, supomos distribuições de probabilidade para os coeficientes da regressão com métodos computacionais e obtemos estimativas a partir de amostras da distribuição a posteriori dos parâmetros, como Monte Carlo Hamiltoniano ou No U-Turn Sampler (NUTS).

A qualidade do ajuste#

A qualidade do ajuste pode ser avaliada com

Análise de diagnóstico

Métricas de ajuste

  • Erro absoluto médio

  • Erro quadrático médio

  • Raiz do erro quadrático médio

Seleção de modelos

  • Método forward

  • Método backward

  • Método stepwise

  • Critérios de informação (AIC, BIC)

Modelos Lineares generalizados#

import numpy as np
import statsmodels.api as sm
from scipy import stats
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt
import seaborn as sns
%matplotlib inline

plt.rc("figure", figsize=(16,8))
plt.rc("font", size=14)

MLG: Dados com resposta binomial#

Referência sugerida: https://www.statsmodels.org/devel/examples/notebooks/generated/glm.html

Aplicação#

Considere os dados do arquivo dados_banco.csv. Estão disponíveis as variáveis:

  • Cliente: Identificador do cliente.

  • Sexo: Feminino (F) ou Masculino (M)

  • Idade: Idade do cliente, em anos completos.

  • Empresa: Tipo da empresa em que trabalha: Pública, Privada ou Autônomo

  • Salário: Salário declarado pelo cliente na abertura da conta, em reais.

  • Saldo_cc: Saldo em conta corrente, em reais.

  • Saldo_poupança: Saldo em poupança, em reais.

  • Saldo_investimento: Saldo em investimentos, em reais.

  • Devedor_cartao: Valor em atraso no cartão de crédito, em reais.

  • Inadimplente: Se o cliente é considerado inadimplente atualmente (1) ou não (0), de acordo com critérios preestabelecidos.

import pandas as pd

# Dados banco - Leitura dos dados
dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/dados_banco.csv', index_col=0)

# Vamos trabalhar com uma amostra como na Aula 6
dados = dados.sample(n=500, replace=False, random_state=10)

dados.head()
Sexo Idade Empresa Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente
Cliente
53080 M 31 Privada 5717.00 1205.56 0.0 0.0 2313.15 0
86540 M 38 Privada 6523.00 1370.21 0.0 0.0 3202.99 0
96211 M 33 Pública 5378.00 750.60 0.0 0.0 4225.13 0
42117 M 34 Autônomo 5496.82 896.48 0.0 0.0 3365.48 1
49964 M 35 Privada 6137.00 774.12 0.0 0.0 4135.15 0

Divisão da base em treino e teste

dados_treino, dados_teste = train_test_split(dados,train_size = 0.8,random_state=3)

Ajustando um MLG com resposta binária#

### Ajustando um MLG com resposta binária, iniciando com 4 preditoras

preditoras = dados_treino[['Idade','Devedor_cartao','Salario','Saldo_cc']]
resposta = dados_treino[['Inadimplente']]
preditoras
Idade Devedor_cartao Salario Saldo_cc
Cliente
56455 35 2370.58 5645.78 881.15
85723 31 2509.16 5560.00 411.92
3478 28 4269.03 5234.00 982.51
31829 34 1196.30 5610.00 1084.03
38218 29 2191.62 5131.00 674.81
... ... ... ... ...
45916 30 5102.12 4957.82 770.16
36647 31 2012.71 5111.68 591.95
2141 29 3895.29 5218.00 1141.87
77304 36 3192.87 5602.00 1234.37
75695 31 4368.80 5578.00 903.62

400 rows × 4 columns

glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:           Inadimplente   No. Observations:                  400
Model:                            GLM   Df Residuals:                      396
Model Family:                Binomial   Df Model:                            3
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -137.45
Date:                Wed, 17 Jan 2024   Deviance:                       274.90
Time:                        05:10:18   Pearson chi2:                     336.
No. Iterations:                     6   Pseudo R-squ. (CS):             0.4037
Covariance Type:            nonrobust                                         
==================================================================================
                     coef    std err          z      P>|z|      [0.025      0.975]
----------------------------------------------------------------------------------
Idade              0.3067      0.099      3.090      0.002       0.112       0.501
Devedor_cartao     0.0007   9.76e-05      7.384      0.000       0.001       0.001
Salario           -0.0015      0.001     -2.609      0.009      -0.003      -0.000
Saldo_cc          -0.0069      0.001     -7.334      0.000      -0.009      -0.005
==================================================================================
ajustado = res.predict(preditoras)
X_teste = dados_teste[['Idade','Devedor_cartao','Salario','Saldo_cc']]
Y_teste = dados_teste[['Inadimplente']]

predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.08727882133957354
res.aic
282.90066363214913

Segundo modelo, excluindo Saldo_cc

preditoras = dados_treino[['Idade','Devedor_cartao', 'Salario']]
resposta = dados_treino[['Inadimplente']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:           Inadimplente   No. Observations:                  400
Model:                            GLM   Df Residuals:                      397
Model Family:                Binomial   Df Model:                            2
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -180.33
Date:                Wed, 17 Jan 2024   Deviance:                       360.67
Time:                        05:10:18   Pearson chi2:                     386.
No. Iterations:                     5   Pseudo R-squ. (CS):             0.2611
Covariance Type:            nonrobust                                         
==================================================================================
                     coef    std err          z      P>|z|      [0.025      0.975]
----------------------------------------------------------------------------------
Idade              0.2325      0.081      2.883      0.004       0.074       0.391
Devedor_cartao     0.0007    8.5e-05      8.093      0.000       0.001       0.001
Salario           -0.0019      0.000     -4.129      0.000      -0.003      -0.001
==================================================================================
ajustado = res.predict()
X_teste = dados_teste[['Idade','Devedor_cartao','Salario']]
Y_teste = dados_teste[['Inadimplente']]

predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.12779719792213348
res.aic
366.6680677035512
n=len(dados_treino)
dados_treino.loc[:,'const']  = np.ones(n).reshape(n,1)
dados_treino.head()
Sexo Idade Empresa Salario Saldo_cc Saldo_poupança Saldo_investimento Devedor_cartao Inadimplente const
Cliente
56455 M 35 Privada 5645.78 881.15 0.0 0.0 2370.58 1 1.0
85723 F 31 Privada 5560.00 411.92 0.0 0.0 2509.16 0 1.0
3478 M 28 Pública 5234.00 982.51 0.0 0.0 4269.03 0 1.0
31829 M 34 Pública 5610.00 1084.03 0.0 0.0 1196.30 0 1.0
38218 F 29 Pública 5131.00 674.81 0.0 0.0 2191.62 0 1.0
preditoras = dados_treino[['Idade','Devedor_cartao', 'Salario']]
resposta = dados_treino[['Inadimplente']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:           Inadimplente   No. Observations:                  400
Model:                            GLM   Df Residuals:                      397
Model Family:                Binomial   Df Model:                            2
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -180.33
Date:                Wed, 17 Jan 2024   Deviance:                       360.67
Time:                        05:10:18   Pearson chi2:                     386.
No. Iterations:                     5   Pseudo R-squ. (CS):             0.2611
Covariance Type:            nonrobust                                         
==================================================================================
                     coef    std err          z      P>|z|      [0.025      0.975]
----------------------------------------------------------------------------------
Idade              0.2325      0.081      2.883      0.004       0.074       0.391
Devedor_cartao     0.0007    8.5e-05      8.093      0.000       0.001       0.001
Salario           -0.0019      0.000     -4.129      0.000      -0.003      -0.001
==================================================================================
res.aic
366.6680677035512

Análise de diagnóstico para o segundo modelo, agora com a base toda

preditoras = dados[['Idade','Devedor_cartao', 'Salario']]
resposta = dados[['Inadimplente']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
ajustado = res.predict()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:           Inadimplente   No. Observations:                  500
Model:                            GLM   Df Residuals:                      497
Model Family:                Binomial   Df Model:                            2
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -219.33
Date:                Wed, 17 Jan 2024   Deviance:                       438.66
Time:                        05:10:18   Pearson chi2:                     473.
No. Iterations:                     5   Pseudo R-squ. (CS):             0.2481
Covariance Type:            nonrobust                                         
==================================================================================
                     coef    std err          z      P>|z|      [0.025      0.975]
----------------------------------------------------------------------------------
Idade              0.2322      0.075      3.079      0.002       0.084       0.380
Devedor_cartao     0.0007   7.74e-05      8.822      0.000       0.001       0.001
Salario           -0.0020      0.000     -4.431      0.000      -0.003      -0.001
==================================================================================
# Gráfico de pontos de alavanca

fig, ax = plt.subplots()

plt.plot(res.get_hat_matrix_diag(), '.')

ax.set_title('Pontos de alavanca')
ax.set_ylabel('$h_{ii}$')
ax.set_xlabel('índice das observações');

plt.show()
../_images/ed31f8c3e4efcf01e5ed299033f7e1d80470e8c1cfdc1398859e57a17e7601a8.png
# Gráfico de Resíduo Componente do desvio

fig, ax = plt.subplots()
plt.plot(ajustado,res.resid_deviance,  '.')


ax.set_ylabel('Resíduo componente do desvio')
ax.set_xlabel('valor ajustado');

plt.show()
../_images/8885d75d1f8e52b31790432c6afee23d7ac1e274591ef1ce590f6422ddb8a425.png
# x and y given as DataFrame columns
import plotly.express as px

fig = px.scatter(x = ajustado, y=res.resid_deviance)
fig.show()

Modelos lineares generalizados: um enfoque Bayesiano#

!pip install arviz
Requirement already satisfied: arviz in /usr/local/lib/python3.10/dist-packages (0.15.1)
Requirement already satisfied: setuptools>=60.0.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (67.7.2)
Requirement already satisfied: matplotlib>=3.2 in /usr/local/lib/python3.10/dist-packages (from arviz) (3.7.1)
Requirement already satisfied: numpy>=1.20.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.23.5)
Requirement already satisfied: scipy>=1.8.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.11.4)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from arviz) (23.2)
Requirement already satisfied: pandas>=1.3.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.5.3)
Requirement already satisfied: xarray>=0.21.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (2023.7.0)
Requirement already satisfied: h5netcdf>=1.0.2 in /usr/local/lib/python3.10/dist-packages (from arviz) (1.3.0)
Requirement already satisfied: typing-extensions>=4.1.0 in /usr/local/lib/python3.10/dist-packages (from arviz) (4.5.0)
Requirement already satisfied: xarray-einstats>=0.3 in /usr/local/lib/python3.10/dist-packages (from arviz) (0.6.0)
Requirement already satisfied: h5py in /usr/local/lib/python3.10/dist-packages (from h5netcdf>=1.0.2->arviz) (3.9.0)
Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (4.47.2)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (1.4.5)
Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (9.4.0)
Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (3.1.1)
Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/dist-packages (from matplotlib>=3.2->arviz) (2.8.2)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas>=1.3.0->arviz) (2023.3.post1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.7->matplotlib>=3.2->arviz) (1.16.0)
pip install --upgrade numba
Requirement already satisfied: numba in /usr/local/lib/python3.10/dist-packages (0.58.1)
Requirement already satisfied: llvmlite<0.42,>=0.41.0dev0 in /usr/local/lib/python3.10/dist-packages (from numba) (0.41.1)
Requirement already satisfied: numpy<1.27,>=1.22 in /usr/local/lib/python3.10/dist-packages (from numba) (1.23.5)
pip show numba
Name: numba
Version: 0.58.1
Summary: compiling Python code using LLVM
Home-page: https://numba.pydata.org
Author: 
Author-email: 
License: BSD
Location: /usr/local/lib/python3.10/dist-packages
Requires: llvmlite, numpy
Required-by: librosa
import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import patsy as pt
import pymc as pm
import seaborn as sns

print(f"Running on PyMC3 v{pm.__version__}")
Running on PyMC3 v5.7.2
%config InlineBackend.figure_format = 'retina'
RANDOM_SEED = 8927
np.random.seed(RANDOM_SEED)
az.style.use("arviz-darkgrid")

Aplicação#

Fonte: https://www.pymc.io/projects/docs/en/v3/pymc-examples/examples/generalized_linear_models/GLM-poisson-regression.html

Este conjunto de dados fictício foi criado para emular alguns dados criados como parte de um estudo do self quantificado, e os dados reais são mais complicados do que isso.

Descrição dos dados

  • O sujeito espirra N vezes por dia, registrado como nsneeze (int)

  • O sujeito pode ou não beber álcool durante o dia, registrado como álcool (booleano)

  • O sujeito pode ou não tomar um medicamento anti-histamínico durante esse dia, registrado como ação negativa denominada (booleano)

  • Os dados são agregados por dia, para produzir uma contagem total de espirros naquele dia, com um sinalizador booleano para o uso de álcool e anti-histamínicos, com a grande suposição de que os espirros têm uma relação causal direta.

  • Crie 4000 dias de dados: contagens diárias de espirros que são distribuídos por Poisson durante o consumo de álcool e o uso de anti-histamínicos

# decide poisson theta values
theta_noalcohol_meds = 1  # no alcohol, took an antihist
theta_alcohol_meds = 3  # alcohol, took an antihist
theta_noalcohol_antihist = 6  # no alcohol, no antihist
theta_alcohol_antihist = 36  # alcohol, no antihist

# create samples
q = 1000
df = pd.DataFrame(
    {
        "nsneeze": np.concatenate(
            (
                np.random.poisson(theta_noalcohol_meds, q),
                np.random.poisson(theta_alcohol_meds, q),
                np.random.poisson(theta_noalcohol_antihist, q),
                np.random.poisson(theta_alcohol_antihist, q),
            )
        ),
        "alcohol": np.concatenate(
            (
                np.repeat(False, q),
                np.repeat(True, q),
                np.repeat(False, q),
                np.repeat(True, q),
            )
        ),
        "antihist": np.concatenate(
            (
                np.repeat(False, q),
                np.repeat(False, q),
                np.repeat(True, q),
                np.repeat(True, q),
            )
        ),
    }
)
df.tail()
nsneeze alcohol antihist
3995 40 True True
3996 30 True True
3997 37 True True
3998 22 True True
3999 33 True True
df.groupby(["alcohol", "antihist"]).mean().unstack()
nsneeze
antihist False True
alcohol
False 1.047 6.002
True 3.089 36.004
g = sns.catplot(
    x="nsneeze",
    row="antihist",
    col="alcohol",
    data=df,
    kind="count",
    height=4,
    aspect=1.5,
)
/usr/local/lib/python3.10/dist-packages/seaborn/axisgrid.py:123: UserWarning:

The figure layout has changed to tight

/usr/local/lib/python3.10/dist-packages/seaborn/axisgrid.py:123: UserWarning:

The figure layout has changed to tight

/usr/local/lib/python3.10/dist-packages/seaborn/axisgrid.py:213: UserWarning:

This figure was using a layout engine that is incompatible with subplots_adjust and/or tight_layout; not calling subplots_adjust.
../_images/9dc833babf555429445cf713aa82cfb2a0c1e0bbb797bceb625eac25064f9aa9.png
fml = "nsneeze ~ alcohol + antihist + alcohol:antihist"  # full patsy formulation
fml = "nsneeze ~ alcohol * antihist"  # lazy, alternative patsy formulation
(mx_en, mx_ex) = pt.dmatrices(fml, df, return_type="dataframe", NA_action="raise")
pd.concat((mx_ex.head(3), mx_ex.tail(3)))
Intercept alcohol[T.True] antihist[T.True] alcohol[T.True]:antihist[T.True]
0 1.0 0.0 0.0 0.0
1 1.0 0.0 0.0 0.0
2 1.0 0.0 0.0 0.0
3997 1.0 1.0 1.0 1.0
3998 1.0 1.0 1.0 1.0
3999 1.0 1.0 1.0 1.0
with pm.Model() as mdl_Poisson:

    # define priors, weakly informative Normal
    b0 = pm.Normal("b0_intercept", mu=0, sigma=10)
    b1 = pm.Normal("b1_alcohol[T.True]", mu=0, sigma=10)
    b2 = pm.Normal("b2_antihist[T.True]", mu=0, sigma=10)
    b3 = pm.Normal("b3_alcohol[T.True]:antihist[T.True]", mu=0, sigma=10)

    # define linear model and exp link function
    eta = (
        b0
        + b1 * mx_ex["alcohol[T.True]"]
        + b2 * mx_ex["antihist[T.True]"]
        + b3 * mx_ex["alcohol[T.True]:antihist[T.True]"]
    )

    ## Define Poisson likelihood
    y = pm.Poisson("y", mu=np.exp(eta), observed=mx_en["nsneeze"].values)

Obtenção das amostras

with mdl_Poisson:
    inf_Poisson = pm.sample(1000, tune=1000, cores=4, return_inferencedata=True)
100.00% [8000/8000 01:12<00:00 Sampling 4 chains, 0 divergences]
/usr/local/lib/python3.10/dist-packages/arviz/utils.py:184: NumbaDeprecationWarning:

The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.

Diagnóstico do modelo

az.plot_trace(inf_Poisson)
/usr/local/lib/python3.10/dist-packages/arviz/utils.py:184: NumbaDeprecationWarning:

The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
array([[<Axes: title={'center': 'b0_intercept'}>,
        <Axes: title={'center': 'b0_intercept'}>],
       [<Axes: title={'center': 'b1_alcohol[T.True]'}>,
        <Axes: title={'center': 'b1_alcohol[T.True]'}>],
       [<Axes: title={'center': 'b2_antihist[T.True]'}>,
        <Axes: title={'center': 'b2_antihist[T.True]'}>],
       [<Axes: title={'center': 'b3_alcohol[T.True]:antihist[T.True]'}>,
        <Axes: title={'center': 'b3_alcohol[T.True]:antihist[T.True]'}>]],
      dtype=object)
../_images/6bce7bf60cf37e0bf6b3f44233c4a5b87a82b4e3eb7e9c0641815706107c38b6.png

Estimação dos parâmetros

np.exp(az.summary(inf_Poisson)[["mean", "hdi_3%", "hdi_97%"]])
mean hdi_3% hdi_97%
b0_intercept 1.044982 0.985112 1.110711
b1_alcohol[T.True] 2.953527 2.745601 3.164516
b2_antihist[T.True] 5.743105 5.381677 6.147220
b3_alcohol[T.True]:antihist[T.True] 2.029927 1.881370 2.190216

Exemplo original por Jonathan Sedar 2016-05-15 github.com/jonsedar

Prática 1#

Modelos Lineares Generalizados#

Modelo para óbitos por COVID-19#

Na base de dados comorbidades.csv, são apresentados dados reais de uma amostra obtida do seade-R (Fonte dos dados originais: seade-R/dados-covid-sp). Estão disponíveis as seguintes informações:

  • Identificação do paciente

  • Município

  • Código do IBGE

  • Idade

  • Sexo (1: feminino, 0: masculino)

  • Óbito (1: sim, 0: não)

  • Comorbidades: asma, cardiopatia, diabetes, doença hematológica, doença renal, doença hepática, doença neurológica, imumodepressão, obesidade, outros fatores de risco, pneumopatia, puérpera, síndrome de down (para cada uma delas 1: presente, 0: ausente)

Os dados faltantes foram excluídos da base original para esta análise específica, considerando que essa exclusão não afeta a representatividade da amostra.

Desenvolva uma análise exploratória para investigar a associação entre idade e óbito, e repita para sexo e óbito.

Ajuste um modelo de regressão logística com intercepto, considerando as preditoras sexo, idade, asma, cardiopatia, diabetes, doenca_renal, obesidade.

import numpy as np
import statsmodels.api as sm
from scipy import stats
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt
import seaborn as sns 
%matplotlib inline
import numpy as np
import pandas as pd
import statsmodels.api as sm
from matplotlib import pyplot as plt
from sklearn.model_selection import train_test_split

dados = pd.read_csv('https://raw.githubusercontent.com/cibelerusso/Estatistica-Ciencia-Dados/main/Data/comorbidades.csv', index_col=0)
dados.head()
nome_munic codigo_ibge idade sexo obito asma cardiopatia diabetes doenca_hematologica doenca_hepatica doenca_neurologica doenca_renal imunodepressao obesidade outros_fatores_de_risco pneumopatia puerpera sindrome_de_down
66 Ferraz de Vasconcelos 3515707 86 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0
97 São Paulo 3550308 62 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0
100 São José dos Campos 3549904 58 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0
207 Mauá 3529401 54 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0
249 Cajamar 3509205 62 0 1 0 1 1 0 0 0 0 0 1 0 0 0 0

Ajustando um MLG com resposta binária com intercepto#

Considere a divisão da base em treinamento e teste, deixando 20% das observações para teste#

# Adicionar uma coluna de uns referente ao intercepto

n=len(dados)
dados.loc[:,'const']  = np.ones(n).reshape(n,1)
dados_treino, dados_teste = train_test_split(dados,train_size = 0.8,random_state=3)
preditoras = dados_treino[['const','idade','sexo','asma','cardiopatia','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                  obito   No. Observations:                  945
Model:                            GLM   Df Residuals:                      937
Model Family:                Binomial   Df Model:                            7
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -575.28
Date:                Wed, 17 Jan 2024   Deviance:                       1150.6
Time:                        02:05:48   Pearson chi2:                     941.
No. Iterations:                     4   Pseudo R-squ. (CS):            0.09382
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          z      P>|z|      [0.025      0.975]
--------------------------------------------------------------------------------
const           -3.3762      0.351     -9.609      0.000      -4.065      -2.688
idade            0.0429      0.005      8.212      0.000       0.033       0.053
sexo            -0.1453      0.144     -1.011      0.312      -0.427       0.136
asma            -0.3512      0.408     -0.860      0.390      -1.151       0.449
cardiopatia     -0.0492      0.146     -0.337      0.736      -0.336       0.237
diabetes         0.2503      0.145      1.721      0.085      -0.035       0.535
doenca_renal     0.5812      0.296      1.961      0.050       0.000       1.162
obesidade        0.6499      0.210      3.096      0.002       0.239       1.061
================================================================================
ajustado = res.predict(preditoras)
X_teste = dados_teste[['const','idade','sexo','asma','cardiopatia','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]

predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.19933046972523166
res.aic
1166.5619547074634

Segundo modelo, excluindo cardiopatia

preditoras = dados_treino[['const','idade','sexo','asma','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                  obito   No. Observations:                  945
Model:                            GLM   Df Residuals:                      938
Model Family:                Binomial   Df Model:                            6
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -575.34
Date:                Wed, 17 Jan 2024   Deviance:                       1150.7
Time:                        02:05:48   Pearson chi2:                     942.
No. Iterations:                     4   Pseudo R-squ. (CS):            0.09371
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          z      P>|z|      [0.025      0.975]
--------------------------------------------------------------------------------
const           -3.3807      0.351     -9.625      0.000      -4.069      -2.692
idade            0.0426      0.005      8.304      0.000       0.033       0.053
sexo            -0.1464      0.144     -1.019      0.308      -0.428       0.135
asma            -0.3497      0.409     -0.856      0.392      -1.151       0.451
diabetes         0.2489      0.145      1.712      0.087      -0.036       0.534
doenca_renal     0.5768      0.296      1.948      0.051      -0.004       1.157
obesidade        0.6495      0.210      3.093      0.002       0.238       1.061
================================================================================
ajustado = res.predict()
X_teste = dados_teste[['const','idade','sexo','asma','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]

predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.19943538803697025
res.aic
1164.6752349551355

Terceiro modelo, excluindo asma

preditoras = dados_treino[['const','idade','sexo','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                  obito   No. Observations:                  945
Model:                            GLM   Df Residuals:                      939
Model Family:                Binomial   Df Model:                            5
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -575.72
Date:                Wed, 17 Jan 2024   Deviance:                       1151.4
Time:                        02:05:48   Pearson chi2:                     942.
No. Iterations:                     4   Pseudo R-squ. (CS):            0.09298
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          z      P>|z|      [0.025      0.975]
--------------------------------------------------------------------------------
const           -3.3842      0.351     -9.651      0.000      -4.071      -2.697
idade            0.0425      0.005      8.295      0.000       0.032       0.053
sexo            -0.1461      0.144     -1.017      0.309      -0.428       0.135
diabetes         0.2539      0.145      1.748      0.081      -0.031       0.539
doenca_renal     0.5806      0.296      1.963      0.050       0.001       1.160
obesidade        0.6365      0.209      3.040      0.002       0.226       1.047
================================================================================
ajustado = res.predict()
X_teste = dados_teste[['const','idade','sexo','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]

predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.19962241839836906
res.aic
1163.430212617864

Quarto modelo, excluindo sexo

preditoras = dados_treino[['const','idade','diabetes','doenca_renal', 'obesidade']]
resposta = dados_treino[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
print(res.summary())


ajustado = res.predict()
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                  obito   No. Observations:                  945
Model:                            GLM   Df Residuals:                      940
Model Family:                Binomial   Df Model:                            4
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -576.23
Date:                Wed, 17 Jan 2024   Deviance:                       1152.5
Time:                        02:05:48   Pearson chi2:                     942.
No. Iterations:                     4   Pseudo R-squ. (CS):            0.09199
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          z      P>|z|      [0.025      0.975]
--------------------------------------------------------------------------------
const           -3.4253      0.348     -9.834      0.000      -4.108      -2.743
idade            0.0420      0.005      8.253      0.000       0.032       0.052
diabetes         0.2648      0.145      1.829      0.067      -0.019       0.549
doenca_renal     0.6034      0.295      2.048      0.041       0.026       1.181
obesidade        0.6230      0.209      2.987      0.003       0.214       1.032
================================================================================

Note que a significância marginal das preditoras que ficaram muda em relação aos modelos anteriores. Neste momento paramos as exclusões e vamos manter as preditoras.

X_teste = dados_teste[['const','idade','diabetes','doenca_renal', 'obesidade']]
Y_teste = dados_teste[['obito']]

predito = res.predict(X_teste)
from sklearn.metrics import mean_absolute_error, mean_squared_error
mean_squared_error(predito, Y_teste)
0.20006018415344806
res.aic
1162.4668937333217

Análise de diagnóstico para o modelo “final”, agora com a base toda

preditoras = dados[['const','idade','diabetes','doenca_renal', 'obesidade']]
resposta = dados[['obito']]
glm_binom = sm.GLM(resposta, preditoras, family=sm.families.Binomial())
res = glm_binom.fit()
ajustado = res.predict()
print(res.summary())
                 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                  obito   No. Observations:                 1182
Model:                            GLM   Df Residuals:                     1177
Model Family:                Binomial   Df Model:                            4
Link Function:                  Logit   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -716.15
Date:                Wed, 17 Jan 2024   Deviance:                       1432.3
Time:                        02:05:48   Pearson chi2:                 1.21e+03
No. Iterations:                     4   Pseudo R-squ. (CS):            0.09374
Covariance Type:            nonrobust                                         
================================================================================
                   coef    std err          z      P>|z|      [0.025      0.975]
--------------------------------------------------------------------------------
const           -3.5240      0.315    -11.194      0.000      -4.141      -2.907
idade            0.0432      0.005      9.445      0.000       0.034       0.052
diabetes         0.2758      0.130      2.121      0.034       0.021       0.531
doenca_renal     0.4378      0.260      1.683      0.092      -0.072       0.948
obesidade        0.6268      0.185      3.397      0.001       0.265       0.988
================================================================================

Qual a interpretação dos parâmetros?

Podemos fazê-la com a razão de chances.

Exemplo:

  • Para a preditora obesidade, \(\exp(0.6268) = 1.87\), o que indica um aumento de 87% na chance de óbito para pacientes com obesidade em relação a pacientes que não apresentam essa característica.

Alguns códigos para a análise de diagnóstico.

# Gráfico de pontos de alavanca

fig, ax = plt.subplots()

plt.plot(res.get_hat_matrix_diag(), '.')

ax.set_title('Pontos de alavanca')
ax.set_ylabel('$h_{ii}$')
ax.set_xlabel('índice das observações');

plt.show()
../_images/34e9b03f9e70dd2455bee98d6b31b736f9c35f0e2bbb5ded2b200dbfbb2fbfe1.png
# Gráfico de Resíduo Componente do desvio

fig, ax = plt.subplots()
plt.plot(ajustado,res.resid_deviance,  '.')


ax.set_ylabel('Resíduo componente do desvio')
ax.set_xlabel('valor ajustado');

plt.show()
../_images/2bef37ece8abeaaaf206c44693193be02a9c208e4ce4a0eb95d2c55a855656ed.png
# x and y given as DataFrame columns
import plotly.express as px

fig = px.scatter(x = ajustado, y=res.resid_deviance)
fig.show()